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:
@@ -139,7 +139,7 @@ is `src/binding.mjs`.
|
||||
| `engine` | `provider`, `model`, `thinking` for pi |
|
||||
| `limits` | `turnsPerDay` (200), `turnTimeoutSeconds` (180), `replyChunkChars` (1900), `inboundMaxChars` (4000) |
|
||||
| `context.files[]` | files appended to pi's system prompt in order, repository-relative and inside the repository (no absolute paths, `..` or symlinks); the Discord block is added after them |
|
||||
| `tools` | optional. `roots[]` of `{name, path, write?}`: absolute directories the seat may read through `list_dir`, `read_file` and `search`; a root with `"write": true` may also be written through `write_file` and `edit_file`; `maxFileBytes` (262144), `maxCallsPerTurn` (8); `web` (optional) `{searxng, maxFetchBytes}` enables `web_fetch` and `web_search` through the named SearXNG instance (https, or http on loopback; `maxFetchBytes` 1048576). Absent means no tools and a pi launch with `--no-tools`. A root may not be `/`, the home directory, a symlink, a path with a dot-prefixed segment, or anything inside or above the data root |
|
||||
| `tools` | optional. `roots[]` of `{name, path, write?, git?}`: absolute directories the seat may read through `list_dir`, `read_file` and `search`; a root with `"write": true` may also be written through `write_file` and `edit_file`; a writable root that is a git work tree may carry `git` `{branch, identity, tokenFile, author, protocol?}` and gains `git_status`, `git_commit`, `git_pull` and `git_push` (`protocol: "vault"` adds `reserve_id`); `maxFileBytes` (262144), `maxCallsPerTurn` (8); `web` (optional) `{searxng, maxFetchBytes}` enables `web_fetch` and `web_search` through the named SearXNG instance (https, or http on loopback; `maxFetchBytes` 1048576). Absent means no tools and a pi launch with `--no-tools`. A root may not be `/`, the home directory, a symlink, a path with a dot-prefixed segment, or anything inside or above the data root |
|
||||
|
||||
Unknown keys, missing fields, wrong types, empty allowlists, a user channel
|
||||
that is not listed and a bot listed as a user all refuse with exit 2. A
|
||||
@@ -213,6 +213,61 @@ tests drive both tools against a local server through an injected
|
||||
resolver and transport, so the fence is tested without the network; the
|
||||
real transport is `node:https` with the same options.
|
||||
|
||||
Git verbs (row 24, `src/git.mjs`, `bin/git-credential.mjs`). A root with
|
||||
`write: true` may carry a `git` object: `branch` (the only branch the
|
||||
verbs work on), `identity` (the seat name, used as the https username and
|
||||
as the lock owner), `tokenFile` (an absolute path to a private 0600 file
|
||||
holding the seat's token; the connector checks the mode and never reads
|
||||
the content), `author` as `Name <email>`, and optionally
|
||||
`protocol: "vault"`. The root must be a work tree (`.git` present). Every
|
||||
verb is one `git` child with a fixed argument list, run inside the root,
|
||||
within 60 s, output capped at 4 KiB and masked (`https://user:pw@` and
|
||||
GitHub token shapes become `<masked>`). Before any verb: the head must be
|
||||
on `branch`, not detached, not mid-merge, rebase, cherry-pick, revert or
|
||||
bisect, with no conflicted path. `git_status(root)` reports branch,
|
||||
ahead/behind and changed paths. `git_commit(root, message, paths)` takes
|
||||
one to fifty paths, each a regular file under the root by the read rules
|
||||
(no dot segment, no symlink), and refuses when the index already holds
|
||||
staged work so a terminal user's half-done commit is never swept in; it
|
||||
stages exactly those paths, refuses when nothing changed, commits as
|
||||
`author` with a `Requested-by:` trailer carrying the Discord author's
|
||||
server name, then pushes at once (Jason's D6). A commit whose push fails
|
||||
is still a commit: the result says `pushed: false` with git's masked
|
||||
message, and the next commit's push carries both. `git_pull(root)` is
|
||||
`pull --ff-only --no-rebase origin <branch>`; a diverged origin or a
|
||||
dirty path refuses with nothing merged. `git_push(root)` pushes that one
|
||||
branch, never force, never tags. The requester comes from the envelope
|
||||
line the connector writes (`requester="…"`), read by the extension on
|
||||
`before_agent_start`; a commit with no requester is refused.
|
||||
|
||||
Credentials: the fleet helper `git-credential-mosaic` serves the Gitea
|
||||
hosts only and declines github.com, and the host's global git config
|
||||
routes github.com to Jason's own `gh` login, so neither may run for Sage.
|
||||
The verbs run git with `GIT_CONFIG_GLOBAL=/dev/null`,
|
||||
`GIT_CONFIG_NOSYSTEM=1`, `GIT_TERMINAL_PROMPT=0`, no askpass, and one
|
||||
`credential.helper` set through `GIT_CONFIG_COUNT`: the package's own
|
||||
`bin/git-credential.mjs`. It answers only `get` over https, reads the
|
||||
token from the path in `MOSAIC_DISCORD_GIT_TOKEN_FILE` (set by the
|
||||
connector for push, pull and reserve only; local verbs never carry it),
|
||||
refuses a symlink, a non-0600 mode or a value that is not a plain token,
|
||||
and writes username and password to git's stdin pipe. The token is never
|
||||
an argument, never in the environment, never in a record; the tests run
|
||||
every verb through a spy spawn and assert no argv holds the token or its
|
||||
path.
|
||||
|
||||
Vault protocol (`protocol: "vault"`, the shared-signals record rules):
|
||||
`write_file` and `edit_file` take the clone lock for the path
|
||||
(`tools/vault_lock.py lock`, TTL 300 s) around the write and release it
|
||||
after; a lock held by another owner refuses the write with that owner's
|
||||
name. `git_commit` runs `vault_lock.py check` on the named paths and then
|
||||
`tools/validate_vault.py`, and refuses with the tool's first lines when
|
||||
either fails. `reserve_id(root, prefix, title)` runs `vault_lock.py
|
||||
reserve` (prefix BUS, PRJ, SS, DEC or REF; title up to 200 characters)
|
||||
and returns the id; the registry line goes into the next commit with the
|
||||
record. These scripts belong to the shared-signals repository, are run as
|
||||
fixed argv inside the root with the seat as owner, and never through a
|
||||
shell. The tests stand in small Python scripts with the same command line.
|
||||
|
||||
## What happens to a message
|
||||
|
||||
1. The gateway delivers `MESSAGE_CREATE`. `authorize` drops it unless the
|
||||
|
||||
Reference in New Issue
Block a user