feat(discord): git verbs for the Discord Sage on the shared-signals root, seat identity through a package credential helper, vault record protocol (#1509)

Row 24. A writable root that is a git work tree may carry a git object in
the binding; the seat then has git_status, git_commit (explicit paths, seat
author, Requested-by trailer from the envelope requester, push at once per
D6), git_pull (ff-only) and git_push (one branch, never force), plus
reserve_id and per-write clone locks under protocol vault. Git children run
with no host config and one credential helper, bin/git-credential.mjs,
reading the 0600 seat token file named in the binding; the fleet helper
serves only the Gitea hosts. Suite 58/58, node 143. rev-code-02 APPROVED
round 1 (#1509 comment 26375, tree 82ab962f).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-18 07:52:35 -05:00
co-authored by Claude Fable 5.1
parent 1685deb423
commit 1949ed8d31
23 changed files with 1284 additions and 48 deletions
+1
View File
@@ -255,3 +255,4 @@ are never rewritten or removed; corrections are new entries.
- 2026-09-13 UTC — coordinator (Claude) — Discord binding reload and per-user channels (#1509, QUEUE rows 19–20): `reload`/SIGHUP with fixed keys refused, `reloads.jsonl`, user `channels` allowlist; suite 41/41, 101 node tests; Carmen enrolled live by reload at 00:03 UTC, her first message pending; row 18 assigned to darkwing by Jason.
- 2026-09-14 UTC — coordinator (Claude) — Discord read-only tools (#1509, QUEUE row 21): `tools` binding key, host pi extension `list_dir`/`read_file`/`search` confined to declared roots, engine settles on `agent_end`, tool calls in the turn record; rev-code-02 round 2 APPROVE (26276) after four round 1 findings were fixed; suite 48/48, 116 node tests; committed locally, not pushed; live check in #sage-admin next.
2026-09-17T02:05:47Z | coordinator (Claude, #1509) | Row 23 Discord writes + web tools: built, round 3 pinned (comment 26361), SearXNG live on loopback, engine held-prompt defect fixed live; row 24 git verbs briefed with D5–D7 ruled | in review, uncommitted, no push
2026-09-18T12:46:17Z | coordinator (Claude, #1509) | Row 23 pushed (90cb31f5..1685deb4); row 24 git verbs, package credential helper, vault protocol and requester envelope built, suite 58/58, node 143, review requested from rev-code-02 | in review, uncommitted, no push
+7 -1
View File
@@ -189,7 +189,13 @@ roots, plus `write_file` and `edit_file` for roots marked `"write": true`
credential shapes), and `web_fetch` and `web_search` when `tools.web`
names a SearXNG instance (https only, public addresses only, three
redirects, capped body, html to text), with each call in the turn record.
Sage has no git; the operator commits from the terminal. `tools` is a fixed key: changing
A writable root that is a git work tree may carry `git` (`branch`,
`identity`, `tokenFile`, `author`, optional `protocol: "vault"`) and
gains `git_status`, `git_commit` (explicit paths, seat author,
`Requested-by:` trailer, push at once), `git_pull` (ff-only) and
`git_push` (one branch, never force), with `reserve_id` under the vault
protocol; git runs with no host config and the package's own credential
helper reading a 0600 token file, never printed. `tools` is a fixed key: changing
it needs a stop and start. Records
under `<dataRoot>/discord/<binding>/`: `inbox.jsonl`, `outbox.jsonl`,
`drops.jsonl`, `admissions.jsonl`, `notices.jsonl`, `reloads.jsonl`, write-once `turns/<id>.json`. Suite:
+29 -11
View File
@@ -19,9 +19,11 @@ fixed argument list, run only in a root that is marked `"write": true`
and is a git work tree. No verb takes free-form arguments.
- `git_status(root)`: branch, ahead/behind, changed paths. Read only.
- `git_commit(root, message, paths?)`: stages the named paths (or every
change under the root when omitted), refuses a dot-prefixed path or a
path outside the root, refuses an empty message or one over 500
- `git_commit(root, message, paths)`: stages exactly the named paths
(one to fifty; the "every change when omitted" form of the first draft
was dropped for section 6's explicit-path rule, since the root is
Jason's own clone), refuses a dot-prefixed path or a
path outside the root, refuses when the index already holds staged work, refuses an empty message or one over 500
characters, refuses when nothing is staged, commits with author
`Sage <[email protected]>` and a trailer naming the Discord author
by role (`Requested-by: Jason` or `Carmen`, never an id). Returns the
@@ -38,18 +40,34 @@ the one the binding names (`"branch": "main"`), so a detached head or
another branch refuses; 60 s timeout; stdout and stderr are captured,
trimmed to 4 KiB and returned as data; exit codes become fixed refusals.
Credentials: the connector already runs with `MOSAIC_AGENT_NAME=sage`.
The engine adds `MOSAIC_GIT_IDENTITY=sage` to the child environment and
the repository's existing helper (`git-credential-mosaic`) resolves the
seat's GitHub token at push time. The token is never read, printed,
journaled or passed as an argument; a push failure returns git's message
with any `https://…@` form masked.
Credentials, as built (2026-09-18; the draft above said the fleet helper
would serve): `~/.mosaic/tools/git/git-credential-mosaic` answers only
the two Gitea hosts and exits silently for github.com, and the host's
global git config sends github.com to Jason's own `gh` login. Neither is
acceptable for Sage, so the verbs run git with `GIT_CONFIG_GLOBAL=/dev/null`,
`GIT_CONFIG_NOSYSTEM=1`, no askpass, no prompt, and one helper set
through `GIT_CONFIG_COUNT`: the package's `bin/git-credential.mjs`,
which answers `get` over https from the 0600 file the binding names in
`tokenFile`. The connector checks the file's mode at load and never reads
it; the path reaches git only for push, pull and reserve, through the
environment, never as an argument. The token is never read by the
connector, printed, journaled or passed as an argument; a push failure
returns git's message with any `https://…@` form and token shape masked.
The D5 identity (seat token, `Sage <[email protected]>`) is unchanged;
only the mechanism that presents it differs from the draft.
Binding (`tools` key, fixed):
The requester in the trailer is the Discord author's server name, which
the connector now writes into the envelope line (`requester="…"`) and the
extension reads on `before_agent_start`. A turn without a requester
cannot commit.
Binding (`tools` key, fixed), as built:
```json
{ "name": "shared-signals", "path": "…/shared-signals", "write": true,
"git": { "branch": "main", "identity": "sage" } }
"git": { "branch": "main", "identity": "sage",
"tokenFile": "…/secrets/github-jetrich-sage.token",
"author": "Sage <[email protected]>", "protocol": "vault" } }
```
Without `git` on a root, no git verbs are offered for it. The prompt
+1
View File
@@ -372,3 +372,4 @@ git history + Gitea issues.
- 2026-09-16 (coordinator, #1509 row 23): part 1 writes built: `write_file`/`edit_file` for roots marked `write: true`, temp file plus rename, same fences as reads plus parent-must-exist, no dot paths, no credential shapes; `enabledToolNames` drives `--tools`, the extension and the check line; suite 49/49. Pinned for rev-code-02 round 1 (tree in the #1509 comment). Live finding: after the prompt fix Sage still repeated "ruling Q16" and "two read-only folders" with zero tool calls, because pi resumed the session that held every earlier refusal; the old session file was archived to the sage evidence dir and the service restarted with a fresh session. Writes and web reach Sage only after row 23 lands in the binding.
- 2026-09-16 (coordinator, #1509 row 23): part 2 web built: `src/web.mjs` with `webFetch` (https only, public addresses only, connection pinned to the vetted address, three re-vetted redirects, 1 MiB cap, html to text) and `webSearch` (SearXNG json, ten results); enabled only when the binding `tools.web` key is set. The full-suite hang was a race in `tests/engine.test.mjs` (busy asserted before `agent_settled`, fake pi never stopped); the test now waits for the settle and stops in `finally`. Suite 52/52, node 128. Round 2 pinned for rev-code-02 (comment 26358, aggregate 287af5da, tree 1721584c). Next: verdict, local commit of my 17 paths only, then SearXNG container on 127.0.0.1:8888 and the live check.
- 2026-09-16 (coordinator, #1509 row 23): part 3 live: SearXNG container `mosaic-searxng` (image searxng/searxng:latest, settings in the data root, formats html and json, limiter off) on 127.0.0.1:8888; binding `tools.web` added (backup in the sage evidence dir); check ok; service restarted. Jason's first turn in #sage-admin searched, fetched who.is, listed the folder and wrote `vault/Businesses/naming.md`. Defect seen in the same exchange: his second message during the turn went to pi as a follow-up, pi folded it into the same run, the first answer was never posted and the second failed as settled-without-turn. Fixed in `engine-pi.mjs` (held prompts, one run each), fake pi now models real follow-up semantics, suite 52/52 node 129, round 3 pinned (comment 26361, aggregate e30c2319, tree dbd2ce9a), service restarted with the fix. Row 24 rulings: D5 seat identity with `[email protected]`, D6 push every commit (Jason: not pushing means stale data), D7 rev-code-02.
- 2026-09-18 (coordinator, #1509 rows 23–24): row 23 committed as 1685deb4 and pushed (`90cb31f5..1685deb4`) at Jason's word. Row 24 built: `src/git.mjs` (git_status, git_commit with explicit paths, seat author and `Requested-by:` trailer, push after every commit per D6, git_pull ff-only, git_push one branch; guard for branch, detached head, in-progress operations and conflicts; index must be empty so Jason's terminal work is never swept), `bin/git-credential.mjs` (the package's own helper: `get` over https from the 0600 token file, since `git-credential-mosaic` serves only the Gitea hosts and the global config routes github.com to Jason's `gh`), git children run with no host config; vault protocol (`protocol: "vault"`): per-write clone lock, `check` and `validate_vault.py` before a commit, `reserve_id`; the connector writes `requester="<server name>"` into the envelope and the extension reads it on `before_agent_start`. Suite 58/58, node 143. Review requested from rev-code-02; binding change and live check follow the verdict.
+2 -1
View File
@@ -47,7 +47,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
| 20 | Discord connector: per-user channel allowlist in the binding and Carmen enrolled (all listed rooms except #sage-admin) (MVP iteration 5) | coordinator | #1509 | done: caaef941 (`users[].channels` allowlist, `channel-not-for-user` drop); Carmen enrolled live by reload 00:03 UTC; her first message is the remaining check | Carmen gets a reply in #general and silence in #sage-admin; Jason unchanged | `2026-09-13_discord-connector-pilot.md` section 11 |
| 21 | Discord connector: read-only tools for the Discord Sage through a Mosaic pi extension confined to declared roots (MVP iteration 6) | coordinator; reviewer per Q12 | #1509 | approved: rev-code-02 round 2 verdict 26276 (tree 43f0329b); committed locally; live check in #sage-admin with Jason next; Jason ruled R1–R7 2026-09-14 (roots docs/ and agents/sage/, Carmen included) | Sage answers a question from a file under a declared root with the reads in the turn record; a read outside the roots is refused and recorded | `2026-09-14_discord-readonly-tools.md` |
| 23 | Discord connector: writes confined to the `shared-signals` root plus web fetch and search for the Discord Sage (MVP iteration 7) | coordinator; reviewer per Q12 | #1509 | in review: parts 1 and 2 (writes, web fetch and search) built 2026-09-16, suite 52/52, node 128; round 3 pinned for rev-code-02 on #1509 comment 26361 (supersedes rounds 1–2; adds the engine held-prompt fix); part 3 done live 2026-09-17 (SearXNG container on loopback, binding web key, restart; first live turn searched, fetched and wrote vault/Businesses/naming.md); Jason ruled D1–D4 2026-09-16 (SearXNG, Jason and Carmen write, any https host, rev-code-02) | Sage writes a naming shortlist into the repository from #ideas with the write and web calls in the turn record; a write outside the root is refused | `2026-09-16_discord-write-and-web-tools.md` |
| 24 | Discord connector: git verbs (status, commit, pull ff-only, push) for the Discord Sage on the `shared-signals` root, seat identity through the existing credential helper (MVP iteration 8) | coordinator; reviewer per Q12 | #1509 | briefed 2026-09-16 at Jason's word ("Sage will need to have git tooling"); D5–D7 ruled 2026-09-16 evening (seat identity, push every commit, rev-code-02); starts after row 23 review | Sage commits and pushes a decision file from #ideas; GitHub shows Sage as author with a Requested-by trailer; no token in any record | `2026-09-16_discord-git-tools.md` |
| 24 | Discord connector: git verbs (status, commit, pull ff-only, push) for the Discord Sage on the `shared-signals` root, seat identity through the existing credential helper (MVP iteration 8) | coordinator; reviewer per Q12 | #1509 | done 2026-09-18: built (src/git.mjs, bin/git-credential.mjs, extension params, envelope requester, context paragraph, vault protocol via reserve_id and per-write locks); suite 58/58, node 143; rev-code-02 APPROVED round 1 (#1509 comment 26375, tree 82ab962f); D5 mechanism changed: the fleet helper declines github.com, so the package ships its own credential helper reading the 0600 seat token file; binding change and live check next | Sage commits and pushes a decision file from #ideas; GitHub shows Sage as author with a Requested-by trailer; no token in any record | `2026-09-16_discord-git-tools.md` |
Start message for row 6, sent from the board to darkwing:
"Read docs/plans/QUEUE.md, then the plan page section "Piece 5: darkwing on
@@ -103,3 +103,4 @@ Gate F or when blocked."
- 2026-09-16 — coordinator: row 23 part 2 (web_fetch, web_search via SearXNG, src/web.mjs) built; engine test busy/settled race fixed in the test; suite 52/52, node 128; round 2 pinned on #1509 comment 26358 for rev-code-02. Row 24 still waits on D5–D7.
- 2026-09-16 — coordinator: row 23 part 3 live (SearXNG container mosaic-searxng on 127.0.0.1:8888, binding web key, restart); Jason's first web turn worked end to end but exposed a live defect: a second message during a turn was sent as a pi follow-up and lost the first answer. Engine now holds it until pi settles. Suite 52/52, node 129; round 3 pinned as #1509 comment 26361; service restarted with the fix. Row 24 rulings D5–D7 recorded.
- 2026-09-17 — coordinator: shared-signals-05 briefed the new id registry and file lock protocol (jetrich/shared-signals 8f0d946); folded into the row 24 brief section 6 (validate before commit, reserve_id tool, per-write clone lock, explicit-path staging). No row changed.
- 2026-09-18 — coordinator: row 23 pushed on Jason's word ("push as well"): refactor 90cb31f5..1685deb4, seven commits, identity jarvis. Row 24 built and under test: four git verbs plus reserve_id, package credential helper (the fleet helper cannot serve github.com), requester in the envelope, explicit-path staging; suite 58/58, node 143; review requested from rev-code-02 on #1509.