feat(discord): writes on write-marked roots, web fetch and search, held prompts (#1509)
Row 23. write_file and edit_file for roots marked write: true under the same fence as reads; web_fetch (https only, public addresses, pinned connection, capped body) and web_search through SearXNG; extension renamed to tools.mjs. Engine holds a prompt while pi is busy and sends it as its own run, so a second message mid-turn no longer folds into the first (live defect). fake-pi models the real follow-up folding. Suite 52/52, node tests 129. rev-code-02 APPROVED round 3, comment 26362, tree dbd2ce9a. Records: QUEUE rows 23-24, CURRENT, BUILD-LOG phase, SESSIONS, row 24 brief (git verbs, D5-D7 ruled). Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# Discord Sage: git for the strategy repository (#1509, QUEUE row 24)
|
||||
|
||||
Jason's word, 2026-09-16, after the first write from Discord landed:
|
||||
"Sage will need to have git tooling to commit, push, pull, etc. for the
|
||||
shared-signals repo." This replaces the row 23 rule that Jason commits
|
||||
from the terminal.
|
||||
|
||||
## 1. Outcome
|
||||
|
||||
A decision reached in Discord ends up committed and pushed to
|
||||
`shared-signals` on GitHub, by Sage, in the same conversation, with the
|
||||
commit and push in the turn record. Sage still has no shell and no git
|
||||
anywhere else.
|
||||
|
||||
## 2. What is built
|
||||
|
||||
Four fixed verbs in the extension, each a `git` child process with a
|
||||
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
|
||||
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
|
||||
short hash.
|
||||
- `git_pull(root)`: `git pull --ff-only origin <current branch>`. A
|
||||
non-fast-forward result is refused with the reason and nothing is
|
||||
merged or rebased.
|
||||
- `git_push(root)`: `git push origin <current branch>`, current branch
|
||||
only, never `--force`, never a tag, never another remote.
|
||||
|
||||
Fences shared by all four: the root's work tree must be clean of
|
||||
conflicts and not mid-merge or mid-rebase; the current branch must be
|
||||
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.
|
||||
|
||||
Binding (`tools` key, fixed):
|
||||
|
||||
```json
|
||||
{ "name": "shared-signals", "path": "…/shared-signals", "write": true,
|
||||
"git": { "branch": "main", "identity": "sage" } }
|
||||
```
|
||||
|
||||
Without `git` on a root, no git verbs are offered for it. The prompt
|
||||
paragraph says which root has git, that a commit is real once pushed,
|
||||
and that Sage reports the hash.
|
||||
|
||||
## 3. Not built
|
||||
|
||||
No shell, no arbitrary git arguments, no branches, no force, no tags, no
|
||||
other remotes, no rebase, no reset, no history rewriting, no git in the
|
||||
Mosaic roots. A pull that needs a merge stops and says so; Jason resolves
|
||||
it from the terminal.
|
||||
|
||||
## 4. Evidence
|
||||
|
||||
- `packages/discord/tests/git.test.mjs` against a local bare remote:
|
||||
status, commit with the trailer, pull ff-only, push; refusals for an
|
||||
empty message, a dot path, a path outside the root, a non-ff pull, a
|
||||
detached head, another branch, a conflicted tree; no token in any
|
||||
argument list or output.
|
||||
- Suite: the extension exposes the four verbs only with a `git` key.
|
||||
- Live: Jason asks in #ideas for a decision to be written, committed
|
||||
and pushed; the GitHub commit shows Sage as author and the trailer;
|
||||
the turn record shows write, commit and push.
|
||||
|
||||
## 5. Rulings from Jason (2026-09-16, evening)
|
||||
|
||||
- D5. Identity: the sage seat's GitHub token (`github-jetrich-sage`
|
||||
in the seat's secrets, resolved by the existing helper), author
|
||||
`Sage <[email protected]>`. Ruled: "That email works is acceptable."
|
||||
- D6. Push every time. Ruled against the recommendation to push only on
|
||||
request: "This will be fatal. Failure to automatically push will result
|
||||
in stale data." So every commit pushes at once; a commit whose push
|
||||
fails is reported as such in the reply and the turn record, and the
|
||||
next commit retries the push.
|
||||
- D7. Reviewer: rev-code-02 on #1509. Ruled: "agree".
|
||||
|
||||
## 6. Shared-signals record protocol (briefed 2026-09-17 by shared-signals-05)
|
||||
|
||||
Verified against `origin/main` of `jetrich/shared-signals` (tooling landed in `8f0d946`; main at `7aa88ad` on 2026-09-17):
|
||||
`tools/vault_lock.py`, `tools/validate_vault.py`, `docs/ID-REGISTRY.txt`,
|
||||
`docs/RECORDS.md` "Reserving IDs and locking files", AGENTS.md step 4.
|
||||
A record's id must be reserved in the registry before the file exists,
|
||||
the registry is append-only and committed, `validate_vault.py` fails a
|
||||
commit whose ids are not registered or are used twice, and a file being
|
||||
edited for more than a moment is locked per clone under `.vault-locks/`
|
||||
(gitignored, fcntl, default TTL 3600 s). Owner comes from
|
||||
`VAULT_LOCK_OWNER`, then `MOSAIC_AGENT_NAME`, then git user.name; the
|
||||
connector already sets `MOSAIC_AGENT_NAME` to the seat, so locks read
|
||||
"sage" with no change.
|
||||
|
||||
What this means for the verbs, since Sage never gets a shell:
|
||||
|
||||
- `git_commit` runs `python3 tools/validate_vault.py` in the root first
|
||||
and refuses the commit, with the validator's first lines, when it fails.
|
||||
It also runs `vault_lock.py check` on the staged paths and refuses when
|
||||
another owner holds one.
|
||||
- A new fixed tool `reserve_id` (prefix BUS, PRJ, SS, DEC or REF plus a
|
||||
title) runs `vault_lock.py reserve` and returns the id; the registry line
|
||||
it appends is staged with the record in the next commit. `write_file` of
|
||||
a new record without a reserved id is not blocked by the connector; the
|
||||
validator catches it at commit, and the prompt tells Sage to reserve
|
||||
first.
|
||||
- `write_file` and `edit_file` take the clone lock for the path around the
|
||||
write (`lock`, write, `unlock`), so a terminal contributor on the same
|
||||
clone sees the claim. A live lock held by another owner refuses the write
|
||||
with that owner's name in the reply.
|
||||
- Staging is by explicit path only: Sage's changed records plus the
|
||||
registry. Jason's own uncommitted files in the same clone (the write root
|
||||
is his clone) are never swept in. `git_pull` is ff-only and refuses when
|
||||
the paths it would touch are dirty.
|
||||
- Push after every commit (D6). A push that fails is said in the reply and
|
||||
retried by the next commit.
|
||||
|
||||
These scripts are Python in the shared-signals repo, not Mosaic code; the
|
||||
connector calls them as fixed argv, never through a shell, and only inside
|
||||
the root marked writable.
|
||||
|
||||
## 7. Order
|
||||
|
||||
After row 23's web tools are reviewed. Git verbs, tests, suite, review,
|
||||
local commit, then the binding change and a live check.
|
||||
Reference in New Issue
Block a user