diff --git a/agents/sage/work/vikunja-setup/comment-mos0845-rulings.mjs b/agents/sage/work/vikunja-setup/comment-mos0845-rulings.mjs new file mode 100644 index 000000000..d51a264c9 --- /dev/null +++ b/agents/sage/work/vikunja-setup/comment-mos0845-rulings.mjs @@ -0,0 +1,46 @@ +// Sage, 2026-10-19: mos-0845 rulings as task comments on P19 379-382, with Sage's own key (bot user 13). The sync key +// reads each comment back. Comments only: no title, description, done, label or bucket change. Prints no secret. +// Usage: node comment-mos0845-rulings.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON +import { readFileSync, writeFileSync, existsSync } from "node:fs"; +const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2); +if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only"); +if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing"); +const SIG = "Added by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13)."; +const COMMENTS = { + 379: [ + "mos-0845 D1: go on the one-registry design as a new queue row. Until it lands, the window plans read `threads --business mosaic-stack --json` and expect [] (agents/sage/work/window-plan/repin-5fa9cc8f2.md, 6dbc67302).", + ], + 380: [ + "mos-0845 D2: a tester message to a role with nobody running is refused, not queued. That is this task's fix. The fix belongs in talk's tester path, before the message is stored, with a test that a tester talk to a role with no running instance exits non-zero and writes no message.", + "Message 02f0e5d6 from the 8e7ea89a1 window stays queued for pm as it is. Any store edit to it waits for Jason.", + ], + 381: [ + "Source for the usage-unknown line, corrected. It is in the bus journal at 2026-10-19T17:29:00Z, not 17:23Z: event usage-unknown, run r8fba7e598156, instance chief, turn 21e204db-8f55-4348-8f13-c10f85e764a2, source @none, code baseline-unknown. Sage read it at 17:32Z with journalctl --user -u 'mosaic-bus@mosaic-stack' --since \"2026-10-19T17:20:00Z\". A bare --since 17:23:30 reads as CDT on dragon-lin (22:23:30Z) and finds nothing; --since \"2026-10-19 17:23:30 UTC\" counts 1. Sage's sage-1003 gave the smoke time, 17:23Z, as the line's time.", + "mos-0845 D3: go. In Window A, Jason adds \"usage-source\": [\"transcript\"] with the live 8e7ea89a1 setter right before activate, and the activate restart loads it. Condition: Darkwing's scratch --plan run for A uses a business file that already has usage-source on and proves both 5fa9cc8f2 and a rollback to 8e7ea89a1 load it.", + ], + 382: [ + "mos-0845 D4: go on the gate change. stack-release.sh accepts one named harness-off refusal for launch.by in place of the launched line. Then Jason runs plugins set harness [\"claude-code\"], then a plain unit restart with --pm, then W6. Writing harness from the target copy before activate can't work, because plugins set goes through the live bus (c216f47db cli.mjs:572). The silent default is refused.", + "The tool change gets an independent review and its own scratch rehearsal, since the blob changes from 4b16e848. Window B stays at ca15d73e9. No window past c216f47db goes to Jason until this task has landed.", + ], +}; +const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim(); +const esc = (s) => s.replace(/&/g, "&").replace(//g, ">"); +async function api(method, path, body, auth) { + const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" }); + const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {} + return { status: r.status, json }; +} +writeFileSync(OUT, "", { flag: "wx" }); +const out = { at: new Date().toISOString(), comments: [] }; +for (const [task, lines] of Object.entries(COMMENTS)) { + const text = [...lines, SIG].map((l) => `

${esc(l)}

`).join(""); + const r = await api("PUT", `/api/v1/tasks/${task}/comments`, { comment: text }, key); + const rec = { task: Number(task), status: r.status, id: r.json?.id, code: r.json?.code, message: r.json?.message }; + const back = await api("GET", `/api/v1/tasks/${task}/comments`, undefined, sync); + rec.readBack = back.status; + rec.found = Array.isArray(back.json) && back.json.some((c) => c.id === r.json?.id); + out.comments.push(rec); + if (!rec.found) break; +} +writeFileSync(OUT, JSON.stringify(out, null, 1) + "\n"); +console.log(JSON.stringify(out)); diff --git a/agents/sage/work/vikunja-setup/mos0845-rulings-2026-10-19.json b/agents/sage/work/vikunja-setup/mos0845-rulings-2026-10-19.json new file mode 100644 index 000000000..843bef89b --- /dev/null +++ b/agents/sage/work/vikunja-setup/mos0845-rulings-2026-10-19.json @@ -0,0 +1,33 @@ +{ + "at": "2026-10-19T17:44:59.064Z", + "comments": [ + { + "task": 379, + "status": 201, + "id": 273, + "readBack": 200, + "found": true + }, + { + "task": 380, + "status": 201, + "id": 274, + "readBack": 200, + "found": true + }, + { + "task": 381, + "status": 201, + "id": 275, + "readBack": 200, + "found": true + }, + { + "task": 382, + "status": 201, + "id": 276, + "readBack": 200, + "found": true + } + ] +} diff --git a/agents/sage/work/window-plan/repin-5fa9cc8f2.md b/agents/sage/work/window-plan/repin-5fa9cc8f2.md index 387d60a48..349925fd7 100644 --- a/agents/sage/work/window-plan/repin-5fa9cc8f2.md +++ b/agents/sage/work/window-plan/repin-5fa9cc8f2.md @@ -91,7 +91,7 @@ takes the `revision` from a read made just before it. | # | Who | Step | |---|-----|------| | W0 | Mos | `cd $REPO && scripts/stack-copy.sh copy 5fa9cc8f2c66b52408b652b59c3070f7210b44d0`. Note the full id it prints. Then P1 for NEW. | -| W1 | Jason, then Mos | PM-idle check. Jason: `mosaic agents --business mosaic-stack`; chief not `working`, pm not running. Mos: `mosaic launches list`. Nobody talks to the chief until W6 passes. Record `date -u +%FT%TZ` as the start stamp. Baseline reads, kept for W6: `$C_OLD names --business mosaic-stack --json` and `$C_OLD threads --business mosaic-stack --json` (both `[]` on 10-19), and the record count from `mosaic usage --business mosaic-stack --json`. | +| W1 | Jason, then Mos | PM-idle check. Jason: `mosaic agents --business mosaic-stack`; chief not `working`, pm not running. Mos: `mosaic launches list`. Nobody talks to the chief until W6 passes. Record `date -u +%FT%TZ` as the start stamp. Baseline reads, kept for W6: `$C_OLD names --business mosaic-stack --json` and `$C_OLD threads --business mosaic-stack --json` (both `[]` on 10-19), and `mosaic usage --business mosaic-stack --json` saved whole, for its `total` and `byRun` (U1, mos-0846). | | W2 | Jason | Usage source, OLD's setter. `$C_OLD plugins --business mosaic-stack --json`; note `revision`. Write PATCH as `{"revision": , "plugins": {"usage-source": ["transcript"]}}`, then `$C_OLD plugins set --file PATCH --business mosaic-stack`. Read again: `usage-source` is `["transcript"]` and no other kind is set. A refusal ends the window with nothing stopped and nothing to undo. | | W2b | Mos | `node $P/business-check.mjs 8e7ea89a1e86447d26ec335e4b138acfd5130cbc` and the same for NEW, on the patched file. Both LOAD_OK. A REFUSED stops the window; undo W2 as below. | | W3 | Mos | `scripts/stack-release.sh activate 5fa9cc8f2c66b52408b652b59c3070f7210b44d0 mosaic-stack --plan`. Writes nothing. Its from, to, store schema, target schema, migration and needs lines must match P3's scratch output: from 8e7ea89a1, to 5fa9cc8f2, store schema 3h read from the store the running bus holds, target 3h, no migration step, no `--migrate` need, 78 commits. The bus, Console and CLI lines carry the pointer's flags; beyond those, only copy paths differ. Any other difference stops the window before W5 and goes to Mos. | @@ -118,7 +118,7 @@ $C_NEW threads --business mosaic-stack --json # rows 128/129: [] mosaic project list --json # exit 0 mosaic limits --business mosaic-stack --json # row 125: exit 0 mosaic business instances mosaic-stack --json # row 165: the file's six instances, nothing added -mosaic usage --business mosaic-stack --json # exit 0, same record count as W1 +mosaic usage --business mosaic-stack --json # total and byRun equal W1's, field for field mosaic launches list # one running chief from this start mosaic agents --business mosaic-stack # pm not running ``` @@ -134,11 +134,17 @@ wrote a name during the window; stop and report. ``` mosaic talk chief "[mos test] re-pin 5fa9cc8f2: reply with one line, no staffing request" --tester -mosaic usage --business mosaic-stack --json # one new record for that turn, source transcript, status known +mosaic usage --business mosaic-stack --json # one new byRun entry, this start's chief run: input > 0, output > 0, unknownTurns 0 ``` -The `usage` read is F4's acceptance. A new record with `status` `unknown` goes to -Mos with its code; it doesn't roll the window back by itself. +The `usage` read is F4's acceptance. `mosaic usage --json` prints no record list, +only `total` and the groups `byRun`, `byInstance`, `byModel`, `byProvider` and +`byAccount` (5fa9cc8f2 `packages/cli/src/usage.mjs`), so the check reads the new +`byRun` entry. A turn counts as known only when the transcript added usage +(5fa9cc8f2 `packages/bus/src/usage.mjs:34` returns `no-new-usage` otherwise), so +nonzero input and output with `unknownTurns` 0 prove the transcript source works. +Any other result goes to Mos with the journal's `usage-unknown` code; it doesn't +roll the window back by itself. There is no pm smoke. pm isn't running, and a tester message to it waits for a claim nobody makes (F3). The 8e7ea89a1 window's message `02f0e5d6` is still diff --git a/agents/sage/work/window-plan/repin-ca15d73e9.md b/agents/sage/work/window-plan/repin-ca15d73e9.md index 6a35488bf..bf82bc684 100644 --- a/agents/sage/work/window-plan/repin-ca15d73e9.md +++ b/agents/sage/work/window-plan/repin-ca15d73e9.md @@ -49,7 +49,7 @@ after the window, with a `launch.limits.tokens.daily` entry, on Mos's go. | # | Who | Step | |---|-----|------| | W0 | Mos | `cd $REPO && scripts/stack-copy.sh copy ca15d73e9fdcdcf66a10f86cdedeb2d6a08f2b00`. Note the full id. Then P1 for NEW. | -| W1 | Jason, then Mos | PM-idle check as Window A. Record the start stamp. Baseline reads: `$C_OLD names --business mosaic-stack --json`, `$C_OLD threads --business mosaic-stack --json` and the record count from `mosaic usage --business mosaic-stack --json`. Nobody talks to the chief until W6 passes, so no turn writes a usage record (D3). | +| W1 | Jason, then Mos | PM-idle check as Window A. Record the start stamp. Baseline reads: `$C_OLD names --business mosaic-stack --json`, `$C_OLD threads --business mosaic-stack --json` and `mosaic usage --business mosaic-stack --json` saved whole, for its `total` and `byRun` (U1, mos-0846). Nobody talks to the chief until W6 passes, so no turn writes a usage record (D3). | | W2 | Mos | `scripts/stack-release.sh status mosaic-stack`: pointer on 5fa9cc8f2, schema 3h, last line ok. | | W3 | Mos | `scripts/stack-release.sh activate ca15d73e9fdcdcf66a10f86cdedeb2d6a08f2b00 mosaic-stack --plan`. It must match P3 on from 5fa9cc8f2, to ca15d73e9, store schema 3h read from the store the running bus holds, target 3i, one migration step 3h to 3i, needs `--migrate`, needs Jason present, 8 commits. Any other difference stops the window before W5. | | W4 | Mos | Drop-ins, as Window A. | @@ -66,21 +66,29 @@ $C_NEW plugins --business mosaic-stack --json # unchanged from Wi $C_NEW names --business mosaic-stack --json # same as W1 $C_NEW threads --business mosaic-stack --json # same as W1 ([] unless Chat was used after A) mosaic limits --business mosaic-stack --json # exit 0 -mosaic usage --business mosaic-stack --json # exit 0, every record from before still listed +mosaic usage --business mosaic-stack --json # total and byRun equal W1's, field for field mosaic launches list # one running chief from this start mosaic agents --business mosaic-stack # pm not running ``` -The `usage` read shows that the 3i migration kept every usage record. The count -must equal W1's. +The `usage` read shows that the 3i migration kept the usage records. `mosaic usage +--json` prints no record list, only `total` and the groups `byRun`, `byInstance`, +`byModel`, `byProvider` and `byAccount` (ca15d73e9 `packages/cli/src/usage.mjs`, +the same blob as 5fa9cc8f2's), so `total` and `byRun` must equal W1's field for +field. ### W7 ``` mosaic talk chief "[mos test] re-pin ca15d73e9: reply with one line, no staffing request" --tester -mosaic usage --business mosaic-stack --json # one new known record, source transcript +mosaic usage --business mosaic-stack --json # one new byRun entry, this start's chief run: input > 0, output > 0, unknownTurns 0 ``` +A turn counts as known only when the transcript added usage (ca15d73e9 +`packages/bus/src/usage.mjs`, the `no-new-usage` return), so nonzero input and +output with `unknownTurns` 0 prove the transcript source still works under 3i. ca15d73e9 +also writes an account on new rows, so W7 doesn't compare `byAccount` with W1. + No pm smoke (F3, P19 380). Jason's browser check: Settings shows Token limits with no daily quota entry. Nobody presses Save. @@ -111,7 +119,9 @@ stamp leave the live store. post-attempt copy the run keeps still holds them, and nobody opens or changes it without Jason. -W1's idle rule and W7's single turn keep the usage count at one record or none. +W1's idle rule and W7's single turn keep the `usage_records` rows after the stamp +to one turn's begin and end rows, or none. This count is read from the store, not +from `mosaic usage`. `mosaic name` stays out of the window (mos-0641), so `instance_names` should have none. @@ -135,29 +145,16 @@ target copy before activate doesn't help either: it goes through the live bus (c216f47db `cli.mjs:572`), which runs the old code. A silent default for a missing `harness` kind is refused (D4). -The fix, 382 (D4). `stack-release.sh` accepts one named `harness-off` refusal for -`launch.by` in place of the `launched` line, for a target that has the harness -kind. It's a tool change, so it gets an independent review and its own scratch -rehearsal; the blob changes from 4b16e848. - -The steps, once 382 has landed: - -1. `activate mosaic-stack --pm`. The target's schema decides - `--migrate`. If Window B is live at 3i, c216f47db doesn't change the schema - (`SCHEMA_VERSION = '3i'` on 894ce3f37), so there's no migration; Jason is - present anyway for step 2. If B isn't live, the target carries 3h to 3i and - needs `--migrate` with Jason (P19 371). -2. Jason: `$C_NEW plugins --business mosaic-stack --json` for the revision, then - PATCH `{"revision": , "plugins": {"harness": ["claude-code"]}}` - with `$C_NEW plugins set --file PATCH --business mosaic-stack`. Read again: - `harness` is `["claude-code"]` and the other kinds are unchanged. -3. Mos: a plain restart of `mosaic-bus@mosaic-stack.service`, still `--pm`. The - chief launches. Check exactly one `launched` line for that start. -4. The W6 reads, then the chief tester smoke. -5. One managed coder session with its manifest attached (row 65's landing - session: module, route, counted model, thinking). - -Mos puts step 2 to Jason when that window opens. +The fix, 382. mos-0845 D4 ruled that the gate accepts one named `harness-off` +refusal for `launch.by` in place of the `launched` line, then Jason's plugins +set, then a plain restart. That can't work as ruled, and Sage reported it in +sage-1007. A refused PM launch takes the bus down: c216f47db +`packages/cli/src/launcher.mjs:657` throws `PM launch refused: ...` with exit 3, +`packages/cli/src/cli.mjs:507-509` closes the host and rethrows, and the unit's +`RestartPreventExitStatus=2 3 4` (`packages/cli/systemd/mosaic-bus.service.in:33`) +keeps it down. The gate's unit and status checks fail before `pm_launched`, and +there is no live bus for Jason's plugins set. The steps wait for Mos's +ruling on sage-1007. The earlier step list is withdrawn until then. ## What Sage ran for this plan