feat(discord): SetSpark record client for the Discord Sage, fixed verbs against setspark-api, connector-verified approvals (#1509)

Row 25, parts 2a and 2b, against the shared-signals contract a5425a2.

Model side: eight fixed verbs in the pi extension (record_list, record_get,
record_create, record_update, resolve_id, open_approval_request,
get_approval_request, create_document), each one HTTP call with arguments
checked before any request. Writes carry an idempotency key
<principal>:<message id>:<call index> and an audit context. The seat key is
read from a 0600 file on every call and never cached, printed or journaled.

Connector side: append-only approval ledger, Approve button and exact
"approve" reply resolved by the connector against the required approvers,
confirmation message posted as button evidence, bind and add_approval through
the service under connector keys, retry of unknown entries on start.

Evidence: node tests 162 pass, scripts/test-discord.sh 63/63. Review by
rev-code-02, round 1 approved (#1509 comment 26467, tree 7872d8c5).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-22 12:59:39 -05:00
co-authored by Claude Fable 5.1
parent 1949ed8d31
commit 43d7574d6a
24 changed files with 2178 additions and 32 deletions
+53
View File
@@ -2553,3 +2553,56 @@ The binding change and the live check follow the local commit; the
binding stays private. Jason's shared-signals clone holds his own
uncommitted files; explicit-path staging leaves them alone, and Sage
cannot commit while his index holds staged work.
## 2026-09-20 — Discord SetSpark record client (#1509, QUEUE row 25)
Before: the Discord Sage's records lived in files under the shared-signals
git root, written with `write_file` and committed with `git_commit`; an
approval was a word in chat. After: record authority sits in the SetSpark
record service (`setspark-api`, NocoDB and Outline behind it, plan v3.2 at
shared-signals 55b2515, contract `stack/api/openapi.json` at a5425a2). A
binding's tools may carry a `setspark` key (`baseUrl`, `keyFile`,
`principal`); the extension then offers eight fixed verbs, each one HTTP
call in `packages/discord/src/setspark.mjs`: `record_list`, `record_get`,
`record_create`, `record_update`, `resolve_id`, `open_approval_request`,
`get_approval_request`, `create_document`. The api key is re-read from a
0600 file on every call (one bare line, or the mint's JSON) and exists
only in the Authorization header. Every write carries an idempotency key
`<principal>:<message id>:<call index>`; a write outside a turn is refused
before any request. Arguments are checked in the client; a stale
`record_update` renders the current revision and the changed fields.
Approvals are the connector's, never the model's. A successful
`open_approval_request` in a turn makes the connector post the proposal
with an Approve button after the reply, journal it to `approvals.jsonl`,
and bind the message id to the request at the service under
`<principal>:<message id>:bind`. A listed approver's button press or exact
`approve` reply is submitted under `<principal>:<event id>:approval` with
one Discord message as evidence: the reply itself, or for a press the
confirmation line the connector posts first (the service keeps
`source_url` unique per approval; the SetSpark coordinator ruled this on
2026-09-20). Anyone else gets one fixed line and a drop entry. Start
retries intent and unknown binds and approvals with the ledger's stored
evidence. The envelope now carries the author id and the message id, read
by the extension for the write keys and the audit `context`.
Contract work: three server operations the plan's prose folded into one
(open without a message, bind a message, add an approval with the bound
message id) were sent to the coordinator and adopted, with a
`request_stale` code and `kind: button|reply` on add_approval.
Evidence: `scripts/test-discord.sh` 63/63 (`/tmp/suites/test-discord-row25.log`),
node tests 162 (`tests/setspark.test.mjs` 11, `tests/approvals.test.mjs`
8, `tests/context.test.mjs` extended), the real-pi probe with a setspark
key listing the reads and the eight verbs with no key exposure. Review:
rev-code-02 APPROVED round 1 on 2026-09-22T17:58:03Z (#1509 comment 26467). Candidate: 20 paths, aggregate 09140edc, tree
7872d8c52e60b1c1ad9293d32b2c6664e1f50896.
Correction recorded: during the doc checks a stray `git stash -q` stashed
the whole working tree, other sessions' files included; it was popped
within the minute, all 44 modified files returned, and the node tests and
the frozen hashes were re-verified afterwards. No file was lost.
Not done: the private binding change (keyFile at Sage's minted key), the
live check in #sage-admin, the push. Outline collections are empty, so
`create_document` answers 503 until BizOps creates them.