docs(records): row 25 SetSpark fix, Discord restart receipt, Gate F rulings
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -3046,3 +3046,31 @@ a startup journal line with HEAD, the scoped dirty state and a runtime file
|
|||||||
digest. Also unchanged from HEAD: a run pi started that never ends holds
|
digest. Also unchanged from HEAD: a run pi started that never ends holds
|
||||||
prompts until each one times out. No connector restart and no push were done
|
prompts until each one times out. No connector restart and no push were done
|
||||||
here.
|
here.
|
||||||
|
|
||||||
|
## 2026-09-26: row 25 SetSpark fix and the Discord restart (#1509, Sage, reviewed by Rocko)
|
||||||
|
|
||||||
|
Before: row 25 (43d7574d) was committed but could not go live. With the
|
||||||
|
`setspark` key added to the live binding, `discord.sh check` passed but listed
|
||||||
|
no SetSpark verbs. The cause: `resolveToolRoots` dropped `tools.setspark`, so
|
||||||
|
neither pi's `--tools` list nor the connector's SetSpark client ever saw it. No
|
||||||
|
test followed the binding to the extension. Passing the validated object
|
||||||
|
through would also have failed, because the extension refuses its
|
||||||
|
`maxResponseBytes` as an unknown key.
|
||||||
|
|
||||||
|
After: 6c06a6f3. The resolved config carries only the four keys the extension
|
||||||
|
accepts, and the extension restores the response cap. The connector's client
|
||||||
|
uses the validated binding object, which keeps the cap. The README now marks
|
||||||
|
`principal` as required. The new test failed on HEAD and passes with the fix.
|
||||||
|
The eight suites passed on an index export (24/90/43/17/14/15/63/18). Rocko
|
||||||
|
approved the diff (4dec1898…) in
|
||||||
|
`agents/rocko/work/row25-setspark-fix-review-2026-09-26.md` (a28df89e…).
|
||||||
|
|
||||||
|
Restart: the one restart Jason approved at 20:20Z, done at 20:58:03Z from
|
||||||
|
6c06a6f3 with `packages/discord` and `agents/sage` clean. Old PID 890894
|
||||||
|
(running since 09-18) stopped cleanly. New PID 499064, pi child 499126,
|
||||||
|
gateway READY at 20:58:04Z, session continued, 0 restarts. The pi child's
|
||||||
|
tools config carries `setspark` (https://api.setspark.io as sage), and the
|
||||||
|
extension enables the eight record and approval verbs from it. Receipt
|
||||||
|
(0600): `restart-row25-20260926T205803Z.txt` in the Sage evidence directory.
|
||||||
|
Jason's live check (one work item, one proposal approved by button) is still
|
||||||
|
to do.
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
diff --git a/packages/discord/README.md b/packages/discord/README.md
|
||||||
|
index d716e695..4703fa86 100644
|
||||||
|
--- a/packages/discord/README.md
|
||||||
|
+++ b/packages/discord/README.md
|
||||||
|
@@ -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?, 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); `setspark` (optional) `{baseUrl, keyFile, principal?}` names the SetSpark record service (https origin, or http on loopback; key file absolute, 0600, read per call, never printed) and turns on connector-verified approvals. 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); `setspark` (optional) `{baseUrl, keyFile, principal, timeoutMs?}` names the SetSpark record service (https origin, or http on loopback; key file absolute, 0600, read per call, never printed) and turns on connector-verified approvals. 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
|
||||||
|
diff --git a/packages/discord/src/binding.mjs b/packages/discord/src/binding.mjs
|
||||||
|
index 587452fd..c9349b81 100644
|
||||||
|
--- a/packages/discord/src/binding.mjs
|
||||||
|
+++ b/packages/discord/src/binding.mjs
|
||||||
|
@@ -360,5 +360,8 @@ export function resolveToolRoots(binding, { dataRoot }) {
|
||||||
|
if (real === data || real.startsWith(data + sep) || data.startsWith(real + sep)) throw new DiscordError(`tool root ${r.name} overlaps the data root: ${r.path}`);
|
||||||
|
return { name: r.name, path: real, write: r.write, ...(r.git ? { git: { branch: r.git.branch, identity: r.git.identity, tokenFile: r.git.tokenFile, author: `${r.git.author.name} <${r.git.author.email}>`, ...(r.git.protocol ? { protocol: r.git.protocol } : {}) } } : {}) };
|
||||||
|
});
|
||||||
|
- return { roots, maxFileBytes: binding.tools.maxFileBytes, maxCallsPerTurn: binding.tools.maxCallsPerTurn, ...(binding.tools.web ? { web: { searxng: binding.tools.web.searxng, maxFetchBytes: binding.tools.web.maxFetchBytes } } : {}) };
|
||||||
|
+ // setspark carries only the keys loadSetsparkConfig accepts; the extension
|
||||||
|
+ // re-validates it and adds the response cap itself.
|
||||||
|
+ const ss = binding.tools.setspark;
|
||||||
|
+ return { roots, maxFileBytes: binding.tools.maxFileBytes, maxCallsPerTurn: binding.tools.maxCallsPerTurn, ...(binding.tools.web ? { web: { searxng: binding.tools.web.searxng, maxFetchBytes: binding.tools.web.maxFetchBytes } } : {}), ...(ss ? { setspark: { baseUrl: ss.baseUrl, keyFile: ss.keyFile, principal: ss.principal, timeoutMs: ss.timeoutMs } } : {}) };
|
||||||
|
}
|
||||||
|
diff --git a/packages/discord/src/cli.mjs b/packages/discord/src/cli.mjs
|
||||||
|
index 93d18805..045f4918 100755
|
||||||
|
--- a/packages/discord/src/cli.mjs
|
||||||
|
+++ b/packages/discord/src/cli.mjs
|
||||||
|
@@ -214,7 +214,7 @@ async function run(opts) {
|
||||||
|
// The connector's own SetSpark client (bind and approvals) uses the same
|
||||||
|
// key as the model's verbs; without a setspark key it has none, and an
|
||||||
|
// approval request from the model is refused.
|
||||||
|
- const api = toolRoots && toolRoots.setspark ? createSetsparkApi(toolRoots.setspark) : null;
|
||||||
|
+ const api = binding.tools && binding.tools.setspark ? createSetsparkApi(binding.tools.setspark) : null;
|
||||||
|
const connector = createConnector({ binding, journalDir, rest, gateway, engine, api, log: warn });
|
||||||
|
let shuttingDown = false;
|
||||||
|
let exitCode = 0;
|
||||||
|
diff --git a/packages/discord/tests/setspark.test.mjs b/packages/discord/tests/setspark.test.mjs
|
||||||
|
index fc613011..f88dfb23 100644
|
||||||
|
--- a/packages/discord/tests/setspark.test.mjs
|
||||||
|
+++ b/packages/discord/tests/setspark.test.mjs
|
||||||
|
@@ -12,7 +12,7 @@ import {
|
||||||
|
loadSetsparkConfig, readKey, idempotencyKey, connectorKey, callApi, renderRefusal, createSetsparkApi, renderRecord,
|
||||||
|
} from "../src/setspark.mjs";
|
||||||
|
import { loadToolsConfig, createToolSet, enabledToolNames } from "../src/tools.mjs";
|
||||||
|
-import { validateBinding } from "../src/binding.mjs";
|
||||||
|
+import { validateBinding, resolveToolRoots } from "../src/binding.mjs";
|
||||||
|
import { makeRoot, rawBinding } from "./helpers.mjs";
|
||||||
|
|
||||||
|
const KEY_A = "ssk_" + "a".repeat(40);
|
||||||
|
@@ -120,6 +120,24 @@ test("setspark config: reaches the tools config and the binding as a fixed key",
|
||||||
|
assert.throws(() => validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", nope: 1 } } })), /unknown key/);
|
||||||
|
});
|
||||||
|
|
||||||
|
+test("setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension", () => {
|
||||||
|
+ const root = makeRoot();
|
||||||
|
+ const docs = join(root, "docs");
|
||||||
|
+ mkdirSync(docs);
|
||||||
|
+ const dataRoot = join(root, "data");
|
||||||
|
+ mkdirSync(dataRoot);
|
||||||
|
+ const kf = keyFile(root);
|
||||||
|
+ const b = validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", timeoutMs: 5000 } } }));
|
||||||
|
+ const resolved = resolveToolRoots(b, { dataRoot });
|
||||||
|
+ assert.deepEqual(resolved.setspark, { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", timeoutMs: 5000 });
|
||||||
|
+ for (const name of SETSPARK_TOOL_NAMES) assert.ok(enabledToolNames(resolved).includes(name), `${name} reaches pi's --tools list`);
|
||||||
|
+ const ext = loadToolsConfig(JSON.parse(JSON.stringify(resolved)));
|
||||||
|
+ assert.deepEqual(ext.setspark, b.tools.setspark, "the extension rebuilds the same config, response cap included");
|
||||||
|
+ const plain = resolveToolRoots(validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }] } })), { dataRoot });
|
||||||
|
+ assert.equal("setspark" in plain, false);
|
||||||
|
+ assert.ok(!enabledToolNames(plain).some((n) => SETSPARK_TOOL_NAMES.includes(n)));
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
test("setspark keys: read per call, one printable token per file, rotation without a restart", async () => {
|
||||||
|
const root = makeRoot();
|
||||||
|
const c = config(root);
|
||||||
@@ -390,3 +390,4 @@ are never rewritten or removed; corrections are new entries.
|
|||||||
- 2026-09-26T20:35:39Z · Dewey (T3, claude-opus-5-5) · #1507 board Host/Origin guard before CHAT-02: serve.mjs foreignRequest + 2 failing-first tests, 7 scratch variants, suites 149/149 · candidate ready for Rocko's review via Sage; uncommitted
|
- 2026-09-26T20:35:39Z · Dewey (T3, claude-opus-5-5) · #1507 board Host/Origin guard before CHAT-02: serve.mjs foreignRequest + 2 failing-first tests, 7 scratch variants, suites 149/149 · candidate ready for Rocko's review via Sage; uncommitted
|
||||||
2026-09-26T20:35:44Z | Dewey (T3 Claude Code, thread 84fb346c) | #1507 board Host/Origin guard before CHAT-02 | Correction: the entry above at 20:35:39Z used the wrong line format; this one restates it. packages/control-board serve.mjs foreignRequest (d0a9bbed) plus two failing-first tests in serve.test.mjs (e01d7bd9); HEAD fails at foreign Host, six mutations each fail, control-board/webui/seat suites 149/149. Packet agents/dewey/work/board-guard/NOTES.md for Rocko via Sage. No commit or push.
|
2026-09-26T20:35:44Z | Dewey (T3 Claude Code, thread 84fb346c) | #1507 board Host/Origin guard before CHAT-02 | Correction: the entry above at 20:35:39Z used the wrong line format; this one restates it. packages/control-board serve.mjs foreignRequest (d0a9bbed) plus two failing-first tests in serve.test.mjs (e01d7bd9); HEAD fails at foreign Host, six mutations each fail, control-board/webui/seat suites 149/149. Packet agents/dewey/work/board-guard/NOTES.md for Rocko via Sage. No commit or push.
|
||||||
2026-09-26T20:38:14Z | Sage (T3 Claude Code, thread 1ef1e4f8) | Integration: board Host/Origin guard | Rocko approved de9ff942 (review 5a12f08e); Sage committed the guard with records after suites on an index export, pushed, and restarted the control board. Discord restart still held on 6b R2.
|
2026-09-26T20:38:14Z | Sage (T3 Claude Code, thread 1ef1e4f8) | Integration: board Host/Origin guard | Rocko approved de9ff942 (review 5a12f08e); Sage committed the guard with records after suites on an index export, pushed, and restarted the control board. Discord restart still held on 6b R2.
|
||||||
|
2026-09-26T20:58:56Z | Sage (T3 Claude Code, thread 1ef1e4f8) | #1509 row 25 SetSpark live, Gate F routing | Found resolveToolRoots dropping setspark; fixed with failing-first test, Rocko approved, committed 6c06a6f3 and pushed; restarted mosaic-discord@shared-signals (890894 -> 499064), pi has the eight SetSpark verbs. Ruled Darkwing's three Gate F questions; Filbert reviews the brief. Jason live check pending.
|
||||||
|
|||||||
@@ -141,3 +141,28 @@ which stay with him. Each item names who decided it and what happened.
|
|||||||
44 sessions before and after. Live checks: a foreign Host on
|
44 sessions before and after. Live checks: a foreign Host on
|
||||||
`/api/board` returns 403, a foreign Origin on `POST /api/reply` returns
|
`/api/board` returns 403, a foreign Origin on `POST /api/reply` returns
|
||||||
403, and the WebUI proxy's `/api/board` returns 200.
|
403, and the WebUI proxy's `/api/board` returns 200.
|
||||||
|
11. **Row 25 was never live.** Before 20:56Z `discord.sh check` passed with the
|
||||||
|
new `setspark` key, but it listed no SetSpark verbs. `resolveToolRoots`
|
||||||
|
dropped `tools.setspark`, so pi never got the record verbs and the
|
||||||
|
connector never built its SetSpark client. Passing the validated object
|
||||||
|
through as-is would also fail, because the extension refuses its
|
||||||
|
`maxResponseBytes` as an unknown key. Sage wrote the fix with a test that
|
||||||
|
fails first (binding to extension round trip). The connector's client now
|
||||||
|
uses the validated object, which keeps the response cap. The README now
|
||||||
|
marks `principal` as required. The eight suites passed on an index export,
|
||||||
|
and the check lists the eight verbs. Rocko reviews the staged diff
|
||||||
|
(`agents/sage/work/row25-setspark-fix.diff`, 4dec1898…). The restart
|
||||||
|
waits on that verdict and the commit.
|
||||||
|
12. **Gate F brief (Darkwing, 08959a05…).** Sage ruled on the three questions
|
||||||
|
Darkwing had marked for Jason:
|
||||||
|
- Gate F is on by default. A missing DB exits 1 and names `--no-t3`.
|
||||||
|
- The `t3:unmapped` row stays.
|
||||||
|
- The 14 old Discord Bot headers stay as recorded and appear only in the
|
||||||
|
JSON diagnostic.
|
||||||
|
|
||||||
|
The 6a uppercase-class fix rides in Gate F. Filbert reviews the brief,
|
||||||
|
and no code starts before the verdict.
|
||||||
|
13. **Discord restarted with row 25 live.** Rocko approved the fix, which is
|
||||||
|
committed and pushed as 6c06a6f3. The restart ran at 20:58:03Z: PID
|
||||||
|
890894 was replaced by 499064, the gateway was READY at 20:58:04Z, and pi
|
||||||
|
has the SetSpark verbs. Jason's live check comes next.
|
||||||
|
|||||||
Reference in New Issue
Block a user