Compare commits

...
Author SHA1 Message Date
jason.woltje 8708927e5e darkwing: migrate window 0cbe2e9b3 to 464010fbe (rows 270 and 271, #1775 and #1776, 3m to 3n). STATE.md, PLAN.md (sage-3151 revision e664fa35), RULINGS.md, step scripts and transcripts, W9b's pm launch proof (pm ra38826f3c6e2 left live), Mos's P3 rehearsal transcripts and the W11 comment bodies; Gitea comments 31009 on #1776 and 31010 on #1775; docs and transcripts only (sage-3135, mos-1385, mos-1405) 2026-10-22 22:53:00 -05:00
jason.woltje 464010fbe4 queue: revs 3435-3436. Row 294 (#1800): Duckworth APPROVE c30998 (3435, candidate 0b3f9c9d). Row 272 (#1777): Scrooge CHANGES c31006 (3436, candidate 0c3dc2ed); queue only 2026-10-22 22:39:36 -05:00
jason.woltje e57bfde734 queue: revs 3407-3434. Row 293 (574, #1805): Gander in-review r1 (3407, failed pre-send 3408), Sage request c30981 (3409/3410), Filbert APPROVE c30982 (3411). Row 292 (#1797): assigned gizmoduck (mos-1387), claim 3413, in-review r1 3419 (3420 failed), Sage request c30992 (3425/3426), Duckworth APPROVE c30975 (3433). Row 294 (#1800): Launchpad claim 3418, in-review r1 3422 (3423 failed), Sage request c30993 (3427/3428). Row 295 (#1801): Morgana claim 3414, in-review r1 3416 (3417 failed), Sage request c30991 resolved (3421/3424), Dewey CHANGES c30994 (3431). Row 272 (#1777): Huey claim 3415, in-review r1 3429 (3430 failed), Sage request c30997 resolved (3432/3434); queue only 2026-10-22 22:34:14 -05:00
jason.woltje 319d9f87ea sessions: Filbert row 293 (Vikunja 574) APPROVE c30982 rev 3411, plus re-added S0 re-check and 574 static review lines; docs only 2026-10-22 22:15:06 -05:00
jason.woltje b23783f395 Rows 270+271 landing (7328da8ca), 571 landing, MVP rows 293-299: BUILD-LOG phase line and Sage SESSIONS line; docs only 2026-10-22 21:34:23 -05:00
jason.woltje e18ad27115 docs: define mixed-format chapter ordering for conversion 2026-10-22 21:34:23 -05:00
jason.woltje 12f8c3089c queue: rows 293-299 briefed (revs 3399, 3401-3406, sage) and row 293 claimed by gander (rev 3400, gander-293-start); queue only 2026-10-22 21:34:06 -05:00
jason.woltje 253346e3c3 queue: rows 293-299 added (574 #1805, FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 #1804, 573 #1806; mos-1371/1372), 208 after 294; 270 round 3 request c30929 and Duckworth APPROVE c30930; 270+271 done at 7328da8ca (mos-1351); queue only 2026-10-22 21:19:12 -05:00
jason.woltje 7328da8ca4 gizmoduck: row 270 (264c, #1775) gate results: run 3 red on case1-accept P10 band (Vikunja 573) and run 4 red on the checkout.test fixture race (Vikunja 574), both old tests unchanged in the range; run 5 GREEN on 53406f3e4 under mos-1369 and sage-3106, all phases and 38/38 mutants killed; BUILD.md only 2026-10-22 21:07:51 -05:00
jason.woltje 84002f960a gizmoduck: row 270 (264c, #1775) gate run 2 second red, fixed under mos-1364 and sage-3093: compactions-budget.test F1 (:39 input 8.5 * MiB is a whole number, now MiB + 0.5) and F2 (the 0o000 window in try/finally; the after-hook ran after setup's rm); K M22 :129, M23 :120, M22 and M15 notes; BUILD.md run 2 2026-10-22 21:07:51 -05:00
jason.woltje f89aaf57fe gizmoduck: row 270 (264c, #1775) gate run 1 red placed (mos-1272): compactions-launch.test's host wires row 271's five clear ops, as session-clear.test does; the clear test's successor never started without them; kill lines +5 in mutants.py K; BUILD.md run 1 2026-10-22 21:07:51 -05:00
jason.woltje d3a49f301a gizmoduck: rows 270+271 combined gate (C2) record before the first run (sage-3073, sage-3086, mos-1275, mos-1351): blob table, timeout table, GREEN and RED, order; code is ac44d0de9's 2026-10-22 21:07:51 -05:00
jason.woltje 562b05b43c gizmoduck: row 270 (264c, #1775) round 4 for sage-3073 and sage-3078: rows 270 and 271 rebased onto 25c09f19f (row 203 plus the O2 docs); mosaic-config.mjs keeps 203's federation key and both compaction keys; BUILD.md round 4 with the range-diff, the three merged files and C1 after 203; not run 2026-10-22 21:07:51 -05:00
jason.woltje 8c28ee7315 gizmoduck: row 270 (264c, #1775) round 3 for duckworth-0226 B4 (sage-3038): one collection opens at most the system config's compactionStreams stream files in name order (default 1024, 32 to 65536, a bad value takes the default and logs), not-collected past it with no cursor for an unopened stream; the missing-stream check uses a Set; readStreams, cap and launcher tests; mutants M22-M24, M10 anchor moved; docs/TOOLS.md has both keys and C1 (N4); BUILD.md round 3 with stream counts, the 231 KB bound, C1 and N5; not run 2026-10-22 21:07:51 -05:00
jason.woltje bce4917154 gizmoduck: row 270 (264c, #1775) round 2 for duckworth-0223 B1-B3 (sage-3030, sage-3031, mos-1352, sage-3033): pi streams read in 1 MiB chunks under a budget from the system config's compactionReadBytes (default 64 MiB, 1 MiB to 1 GiB, a bad value takes the default and logs), not-collected past it; Claude history from before the ledger is harness-unsupported for good; launcher, runtime reader, clear and legacy tests; mutants M13-M21; BUILD.md round 2 with sizes, schema change, rollback hazard, N1-N3; not run 2026-10-22 21:07:51 -05:00
jason.woltje 01b57f48bb gizmoduck: row 270 (264c, #1775) round 1 for review (sage-3024): mutants.py M1-M12 with anchors at 7d199f033, each matching once, kills read from the tests; run plan with broker.mjs reruns; launcher and runtime wiring named as untested; not run 2026-10-22 21:07:51 -05:00
jason.woltje 54ec0005e0 row 271 (264d, #1776): BUILD.md r3 gate on 9e18a9aaf (sage-3017): cli and bus session-clear.test whole green (5/5, 19/19), cli suite 874/874, M14 killed by the T6 test's first lines() assertion at :365; no first-run red. Doc only. 2026-10-22 21:07:51 -05:00
jason.woltje 61e262b7c0 row 271 (264d, #1776): r3 packet carries mutants.py, M1-M14 with their anchors, as the gate applies them (duckworth-0212 C1). Doc and harness only; no src or test change. 2026-10-22 21:07:51 -05:00
jason.woltje 540b6f16ba row 271 (264d, #1776): r3, T6 per sage-3001. New cli test: a bus unavailable (broker-exited) at the exit proof, then at retirement, holds the clear with no retirement, successor launch or slot handed on, logs once, and resumes when the bus answers. BUILD.md maps the halt case to session-launch.test.mjs:372 and launcher.mjs:824, binds the Q198 hold to row 257 with duckworth-0210's carry, and names M14 (drop clearPass's try/catch) and its kill. No src change. Not run (no CLEAR). 2026-10-22 21:07:51 -05:00
jason.woltje e6a675ee83 row 271 (264d, #1776): BUILD.md gate results on e8af389e6 (sage-2991): 18 touched files, bus/seat/cli suites and every test-*.sh green, M1-M13 killed by assertions in the named tests; the row gate's T6 coverage open to Sage. Doc only. 2026-10-22 21:07:51 -05:00
jason.woltje e77635cebb row 271 (264d, #1776): BUILD.md run limits for the e567f949c gate (mos-1275, sage-2991); mutant lines moved one down past the B1 comment. Doc only. 2026-10-22 21:07:51 -05:00
jason.woltje 3fc8ccef1a row 271 (264d, #1776): duckworth-0201 B1: #ownerRefusal passes 'session.clear' to #granted, so the project and instance sit in their own slots and a live collaborator grant passes the retirement recheck. N4 setup shared; a second N4 test keeps the grant and expects retired plus the successor; M13 restores the defect. 264e carry noted at the N4 tests. Not run (no CLEAR). 2026-10-22 21:07:51 -05:00
jason.woltje 27d3266fff row 271 (264d, #1776): BUILD.md records mos-1336, Mos's approval under mos-1292 of the later3l, rows() and thread-shares migration test changes on the 3m to 3n rebase, with its conditions 2026-10-22 21:07:51 -05:00
jason.woltje 6bd67225b5 row 271 (264d, #1776): BUILD.md states the post-rebase '3m' grep result as measured 2026-10-22 21:07:51 -05:00
jason.woltje 14715d644a row 271 (264d, #1776): r2 on 0cbe2e9b3 (sage-2968): 3m to 3n migration and 3m engine tests from fixtures/schema-3m.sql; BUILD.md records the label change, the old-test changes (later3l, rows() and the thread-shares migration test outside the mos-1315 list, for Mos) and the mutant lines on the rebased tree 2026-10-22 21:07:51 -05:00
jason.woltje f9a9c9b5a0 row 271 (264d, #1776): BUILD.md defines M1-M12 with their expected kills 2026-10-22 21:07:51 -05:00
jason.woltje 34388223d8 row 271 (264d, #1776): launcher uncertain-path test with test-driven ticks and a stopped runner; start() takes stopTimeoutMs 2026-10-22 21:07:51 -05:00
jason.woltje 3adb391b57 row 271 (264d, #1776): N4 failed-clear test on a user's thread with raw first rows; BUILD.md notes the failed path has no verb route yet 2026-10-22 21:07:51 -05:00
jason.woltje 4da907cd5e row 271 (264d, #1776): T4 tests round 1, not run (slot held). Bus session-clear.test.mjs (15), cli session-clear.test.mjs (3, bwrap) with clear-adapter.mjs; BUILD.md maps each T4 item and names what is still open (failed clear, launcher uncertain path, mutants) 2026-10-22 21:07:51 -05:00
jason.woltje aa8fc490f5 row 271 (264d, #1776): clear context build round 1, not run (slot held). Broker: session.clear, session.cancel and session.operation, the fence on send/open/claim, cancel at arrival for routed messages, the drained receive and the end drain, trusted ops sessionClears and sessionClearExited/Uncertain/Retire/Start. Launcher: clearPass before the threads loop (stop, proof, retire, successor start through check()/start()), waiting on a live row 269 survivor. Old tests per mos-1315, rebased on row 206 (sage-2968): label 3m to 3n, future step 3n to 3o, 3m-3n.sql in the engine copies and file lists, ready lines, later3l and the rows() exclusions gain both tables as 206 did for thread_shares (mos-1292, approved in mos-1336), schema.test counts 25 tables and 96 triggers, COUNTED and T45 LISTED gain both tables after thread_shares (mos-1318, mos-1319), TESTER_VERBS pin (mos-1325), fixtures/schema-3m.sql 2026-10-22 21:07:51 -05:00
jason.woltje a9884dc75c row 271 (264d, #1776): schema step 3m to 3n (rebased on row 206's 3l to 3m, sage-2968), session_ops and message_cancels with 7 triggers; successor id pinned as a lowercase version 8 UUID for Huey (gizmoduck-0274) 2026-10-22 21:07:51 -05:00
jason.woltje 25c2c1a560 gizmoduck: row 271 design: duckworth-0171 C1 (start stages keyed on a report counter, work attempt in its own column) and C2 (cancelled in the stage CHECK at attempt 0 and closing the op, claim and cancels untouched by a pre-retire cancel, post-retire successor reading stated, race and held tests); N1 grep at build 2026-10-22 21:07:51 -05:00
jason.woltje 4f827a5e81 gizmoduck: row 271 design: session.cancel and the cancelled stage built in 271 (sage-2828 re-rules Q3 per D7:263-264), pre- and post-retire paths, cancel tests and the fence-release mutant; N2 refuses session-name-too-long at accept 2026-10-22 21:07:51 -05:00
jason.woltje 7dc8ff1bfa gizmoduck: row 271 design: T45 LISTED gains session_ops and message_cancels to match COUNTED (mos-1319) 2026-10-22 21:07:51 -05:00
jason.woltje 61df3061ed gizmoduck: row 271 design: duckworth-0170 B1-B4 folded (cancel at arrival for routed messages, claim refused while clearing, op namespace both ways, attempt key on start stages) and N1-N8 answered; cancel recorded as a departure from D7:263-264 2026-10-22 21:07:51 -05:00
jason.woltje b212182ed1 gizmoduck: row 271 design: both new tables join COUNTED in stack-release.sh, so T46 passes unchanged; schema.test count pins name the 2 tables and 7 triggers; T45 LISTED question to Mos (mos-1318) 2026-10-22 21:07:51 -05:00
jason.woltje dac64ff311 gizmoduck: row 271 (264d) design draft 2: drained acknowledged by the run's own receive at a turn boundary (a landing turn has unread 0 with a child running), SIGTERM only after it; successor <old>-N bound at request with a deterministic id; no cancel (sage-2826); pre-existing test change list per mos-1315 2026-10-22 21:07:51 -05:00
jason.woltje b329ad69f5 gizmoduck: row 271 (264d) design draft 1 in the packet: the threadIdle fence as the drain, the idle stop as the only signal, a session_ops journal and message_cancels in one schema step; Q1-Q4 for Sage and Mos 2026-10-22 21:07:51 -05:00
jason.woltje 8d4d4ec598 gizmoduck: row 270: the bus legacy test inserts its legacy thread by hand (sessions is append-only) and the test business has the assistant thread.create needs; not run 2026-10-22 21:07:51 -05:00
jason.woltje 5ddc36ca97 gizmoduck: row 270 (264c, #1775): completed Pi compactions counted into a host ledger beside the session store, keyed (entry id, timestamp) store-wide (sage-2806, sage-2814); launcher collects at run start and end under the workspace lock; session.list rows gain compactions through a runtime-injected reader; not run (slot held) 2026-10-22 21:07:51 -05:00
jason.woltje f510d73676 gizmoduck: rows 270 and 271 packets: the 264c count and 264d clear contracts pinned for row 272 (Huey), and the Pi 0.85.1 compaction source contract 2026-10-22 21:07:51 -05:00
jason.woltje 767bb62c7d docs: close remote credential and private review access findings 2026-10-22 21:06:29 -05:00
jason.woltje 6345aae3d8 docs: align editor graph, Markdown transition and archive with PRD d6 2026-10-22 21:06:29 -05:00
jason.woltje ee5f7b9ef8 docs: align O2 chapter conversion and Case 2 access with owner rulings 2026-10-22 21:06:22 -05:00
jason.woltje 0b923a2759 MVP readiness briefs (mos-1371/1372): FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 grant scope #1804, 574 #1805, 573 #1806; docs only 2026-10-22 20:32:37 -05:00
jason.woltje b0a6838ca6 SESSIONS: Filbert Q195 O2 S0 review (#1798, c30914); doc only 2026-10-22 19:05:06 -05:00
133 changed files with 12182 additions and 893 deletions

No files matched your search

+14
View File
@@ -5366,3 +5366,17 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
2026-10-22T23:23:42Z | Rocko, W4 #1799 | Before, D24-D26 had no separate exact DOCX citation/access contract. After, 15e91538f proposed a versioned schema, parser/story/view/refusal rules, live authorization/sharing and generic/git/session bypass closure, compatibility gates and 22 acceptance rows. Two JSON examples parse, diff check clean; no code or implementation acceptance. Megavolt review pending.
2026-10-22T23:55:11Z | Sage, W4 #1799 docs landing | Megavolt reviewed four rounds (CHANGES 15e91538f, 019a0ea5b, 54246cb32; APPROVE 994c3f7fb, #1799 c30868/c30870/c30876/c30884). Landed here as cherry-picks: docs/plans/2026-10-22_q195-docx-citations.md blob 2af460ab7 and docs/reviews/2026-10-22_q195-docx-citations.md blob bedde1716, both new files. 32 acceptance rows go to W4b; R4-N1 (re-migration after a pre-W4b restore keeps the rotated fence and needs owner confirmation) goes into W4b's brief and A32. W4 is design-approved only: its gate still needs Sage's W4b implementation brief, which waits for S0 (#1798) PASS. Q36 open, adoption closed. Docs only, no test reads these files.
2026-10-23T00:52:58Z | Gyro Q235-Q237 policy rewrite started, Vikunja 571, mos-1366/1367 | Before: approved O2 e36bce352 keeps Markdown originals at their paths and Q36 notices open; landed W4 2af460ab7 has the same boundary. Imported that byte-identical W4 baseline as 1ac76a417, from refactor 062646487, to make the policy-only revision diff explicit. Prepared P1-P6/B1-B4: human one-way per-story import, human-set source-repo archive, DOCX current reader, scoped format map/archived non-canon reads, W7 conversion publication after W4b/approved PRD/10-27 and Jason first-real-book go. Q234 exact CSP, mos-1363 and S0 M1-M14 retained. W4 locator/extractor, shares and restore-fence blocks compare byte-identical to the approved baseline. Scope/source review ongoing; Mos impact map must be folded before Fenton handoff. No build/browser/worker run, code, queue, private-book or live action.
2026-10-23T01:37:07Z | Gyro Vikunja571 final impact-map / mos-1372/1375 fold | Prepared parent/W4 rewrite against brain-redux d183246 impact map at stack 062646487. P1-P6/B1-B4 and engineering Q1-Q16 are mapped to eleven briefs. D9 replaces the earlier private O2-only context/per-document/share model: normal Case 2 access in any authorized thread/room, supporting writes intact, changed policy awaits PRD 1.17 approval. New DOCX story creation has the same opening gate as human per-story import. Host-only push to human-set off-host remote replaces the no-remote ban; remoteURL defaults unset and same-host bare is disallowed. W7 gates on Hermes #423 step5, W4b, PRD approval, post-10-27 and first-real-book go; owns conversion events COUNTED/--events/pre-conversion restore reconciliation, manifest order/next_chapter, creative role cite wording, archive/old-path edits and format/link/citation routing. Store+off-host acknowledgment precedes archive commit/push, then catalogue publication; post-push failures remain resumable pending with pinned source. Current checks pass: complete brief fields, policy/map coverage, exact Q234 CSP, locator/projection mechanics byte-equivalent except explicit authority wording, A01-A35 IDs, immutable map pin, four-doc scope and append-only logs. W4 authority/remote/recovery sections reopened for Fenton; older APPROVE not reused. No product/build/browser/worker/private-book/queue/live action. Earlier entry describing preserved shares/fence applies only to the pre-D9 intermediate draft; D9 now withdraws those unimplemented structures.
2026-10-23T01:37:55Z | Gyro 571 push guard | identity gyro/purpose push: exit 0 documented degraded-pass CANNOT_ASSERT credential-unresolvable, scratch audit gyro-q195-ci-queue-audit.jsonl; no credential fallback or CI-green claim.
2026-10-23T01:41:59Z | Gyro sage-3121 A1-A3 PRD alignment | Before: 8d209016b adds a current Connections graph, excludes new Markdown stories in converted books and defaults archiveFolder. After: parent/W4 align with PRD1.17 d6 sha256 886b93c571f87bfbb2f27ee3113b6800a32d8f89196d95ace33566031bee380a: clickable editor links only; graph/tests later pending J-b; new different-path Markdown in a converted book remains canon until import, only recreated converted old paths are unmapped; archive folder is human-set in library mapping with no default, missing folder refuses conversion. Static A1-A3/11-brief checks pass; W4 locator/extractor/access/remote/recovery blocks unchanged. Local commit only, push held for Sage's named gap while Gizmoduck holds 270+271 run5. No product/build/browser/worker/queue/live action. Fenton gets local SHA through shared object store.
2026-10-23T01:49:08Z | Gyro fenton-0012 B1/B2 and N1-N6, mos-1379 | B1: off-host remote must deny the real shared jason.woltje seat-helper credential as well as seat identities until #431; private visibility/dummy-token denial is insufficient; W3b/A16/A26 require that future fixture proof. B2 follows mos-1379: human-only editor.agentReadComments and editor.agentReadTrackedDeletions both default false, comment/original/markup reads/resolves/index/context refuse when off; final access remains D9 and no role allowlist/per-document grant. N1 staging-ref allowlist and retained private refused/published audit refs; N2 URLs literal with fidelity-report loss and confirmation; N3 future paths marked new or predecessor row; N4 rename/sharing wording fixed; N5 historical thread text not refreshed; N6 protected VM1012 backup cited in P5. Static checks pass, locator JSON/package/projection mechanics retained. Local commit only; HTTPS push still held for Sage's named gap. No credentials tested/read/printed, no product/build/browser/worker/queue/live action. Fenton gets exact local SHA/blobs.
2026-10-23T01:53:25Z | Gyro fenton-0013 N7 mixed-format chapter order | Fenton approved local 0f4fa4f93. D10 now explicitly orders every chapter ID regardless of Markdown/DOCX format, inserts new canon Markdown by row216 or human committed order, and preserves ID/position at import. W7 tests cover mixed traversal/insertion/import stability. Static diff/append-only checks and docs hook pass. Local commit only, narrow re-check requested, HTTPS push still held for Sage gap. No runtime, queue or live change.
2026-10-23T02:21:06Z | Sage, rows 270+271 landing, 571 landing, MVP rows | Before: 270 (#1775) and 271 (#1776) were approved on gizmoduck/row270-271-r4 49cee846e and 2b964a082 after combined gate C2 run 5 GREEN on 53406f3e4 (bus 873, seat 202, cli 905, ten scripts, 38/38 mutants). After: 33 commits cherry-picked onto 767bb62c7 as 7328da8ca, range-diff all "=", 40 paths identical to the candidate; 270 manifest f4174458 matches, 271 manifest 3ab1f5c8 matches on 29 paths, the other 4 moved under 270 and match its manifest. Done revs 3396/3398, #1775 c30962 and #1776 c30964 closed, P19 463 c861. 571 (Gyro, Fenton APPROVE) picked as ee5f7b9ef, 6345aae3d, 767bb62c7 (blobs 9382f85b, 7ec0897b), 1ac76a417 left out; N7 911dad2aa (Fenton-0014) picked after. Rows 293-299 added (574, FED R6, R4, R7, GitHub grant, Q228, 573), queue commit 253346e3c. Correction: the push sync step checked out O..S paths over the canonical worktree without testing for uncommitted edits, so any pending SESSIONS or BUILD-LOG line in the canonical checkout at 02:06Z would have been overwritten; the sync now skips edited paths. Schema 3n is not live (LIVE 0cbe2e9b3 at 3m).
@@ -0,0 +1,2 @@
chief r148e72c8895d claude-code since 2026-10-22T20:16:24.301Z
# exit 0
@@ -0,0 +1,2 @@
r148e72c8895d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-22T20:16:24.291Z running
# exit 0
@@ -0,0 +1,6 @@
W1 threads --tester: 5 thread(s), threads.json sha256 07c154d75d929028
W1 thread d0ba7a66-aa3f-4918-9518-94132bce6b28 messages 2 sha256 985ac61ca9220f2f
W1 thread 446201de-e8b5-4df3-9356-d0d5422cc899 messages 2 sha256 7e7e9eeeaa8c1e09
W1 thread b89fcca4-6b82-48bd-9310-fb82c5ae8518 messages 2 sha256 f4e4916b9656a403
W1 thread d9bb7ff3-7a8b-4bb8-ba12-03ca094cee4e messages 2 sha256 728ff36f3e003c63
W1 thread b85a57fe-84f9-4785-a596-135af39e334a messages 2 sha256 727de0d9c809a41a
@@ -0,0 +1,2 @@
chief r148e72c8895d claude-code since 2026-10-22T20:16:24.301Z
# exit 0
@@ -0,0 +1,2 @@
r148e72c8895d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-22T20:16:24.291Z running
# exit 0
@@ -0,0 +1,2 @@
chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z
# exit 0
@@ -0,0 +1,6 @@
W6 threads --tester: 5 thread(s), threads.json sha256 07c154d75d929028
W6 thread d0ba7a66-aa3f-4918-9518-94132bce6b28 messages 2 sha256 985ac61ca9220f2f
W6 thread 446201de-e8b5-4df3-9356-d0d5422cc899 messages 2 sha256 7e7e9eeeaa8c1e09
W6 thread b89fcca4-6b82-48bd-9310-fb82c5ae8518 messages 2 sha256 f4e4916b9656a403
W6 thread d9bb7ff3-7a8b-4bb8-ba12-03ca094cee4e messages 2 sha256 728ff36f3e003c63
W6 thread b85a57fe-84f9-4785-a596-135af39e334a messages 2 sha256 727de0d9c809a41a
@@ -0,0 +1,2 @@
chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z
# exit 0
@@ -0,0 +1,7 @@
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=ae0b17c9586e51da7bc1b7e548c6acf913a55665 files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=ae0b17c9586e51da7bc1b7e548c6acf913a55665 length=99 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
# exit 0
@@ -0,0 +1,19 @@
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r28dbbc5c6b4d",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 3811352,
"startTime": "82835140",
"runnerPid": 3811353,
"runnerStartTime": "82835140",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r28dbbc5c6b4d",
"startedAt": "2026-10-23T03:46:01.221Z",
"live": true
}
]
@@ -0,0 +1,2 @@
r28dbbc5c6b4d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-23T03:46:01.221Z running
# exit 0
@@ -0,0 +1,21 @@
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
# exit 0
@@ -0,0 +1,21 @@
B0 (3n) start 2026-10-23T03:47:25.189Z
/home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z:
total 24
drwx------ 3 jwoltje jwoltje 4096 Oct 22 22:47 .
drwx------ 16 jwoltje jwoltje 4096 Oct 22 22:47 ..
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:47 bus
-rw------- 1 jwoltje jwoltje 37 Oct 22 22:47 CHECK
-rw------- 1 jwoltje jwoltje 3 Oct 22 22:47 SCHEMA
-rw------- 1 jwoltje jwoltje 77 Oct 22 22:47 SHA256SUMS
/home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z/bus:
total 3032
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:47 .
drwx------ 3 jwoltje jwoltje 4096 Oct 22 22:47 ..
-rw------- 1 jwoltje jwoltje 3096576 Oct 22 22:47 bus.sqlite
SHA256SUMS: 69702ac9a74ec7d52a7377565c1aa2a2db605fa1f5b9e9f6b38631df3acffebf bus.sqlite
SCHEMA: 3n
CHECK: ok events 2202 maxSeq 2202 schema 3n
B0 path /home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z
B0 SHA256SUMS sha256 0946b66ccde18f5be8d49ed9f15cccf09f73804abdf31b3ae9c108c6c9ee9272
B0 end 2026-10-23T03:47:25.308Z B0_PASS
@@ -0,0 +1,3 @@
r28dbbc5c6b4d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-23T03:46:01.221Z running
ra38826f3c6e2 mosaic-stack/pm claude-code claude-opus-5-5 by chief since 2026-10-23T03:47:35.835Z running
# exit 0
@@ -0,0 +1,6 @@
sent c66b0184 to chief
chief (RESULT, re c66b0184) 11e140fa:
I passed your request to the pm in your own words (message 3479e027-0099-4ce8-a106-0b1115e00dd2), with your note that it's a test and authorizes nothing. You asked for the PRD version and the number of ids in packages/tasks/requirements.json, read through its requirements read.
No pm session was running when I sent it, so I launched one (run ra38826f3c6e2, claude-code, claude-opus-5-5). Nothing was refused. When the pm answers, I'll pass on what it says.
# exit 0
@@ -0,0 +1,4 @@
sent a2d9eeb9 to pm
pm (RESULT, re a2d9eeb9) 4500c1dd:
Version 1.15, 85 ids (checked 10 seconds ago with list_requirements and against packages/tasks/requirements.json at 464010f).
# exit 0
@@ -0,0 +1 @@
464010fbe4c6925b077b9b8182a209ee2177eb9d
@@ -0,0 +1,69 @@
# P1 start 2026-10-23T03:41:42.528Z, REPO HEAD 464010fbe4c6925b077b9b8182a209ee2177eb9d
# plan sha256 e664fa3553f9da0bb29905763011223e69c426a56e484ecfd8181743b7628ad1, P0 recorded e664fa3553f9da0bb29905763011223e69c426a56e484ecfd8181743b7628ad1; NEW 464010fbe4c6925b077b9b8182a209ee2177eb9d
# business-check.mjs sha256 eece4d4671e1ce15ba81e1654c641925ac4988b343687b062897392285a8a1a7; SCRIPTS.sha256 line: eece4d4671e1ce15ba81e1654c641925ac4988b343687b062897392285a8a1a7 business-check.mjs
# business file sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93, mtime 2026-10-19 20:39:58.506635141 -0500
## P1 status
# start 2026-10-23T03:41:42.538Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh status mosaic-stack)
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3m","activation":"20261022T201621Z-0cbe2e9b35e5","at":"2026-10-22T20:16:24Z"}
log (last five lines):
{"at":"2026-10-22T04:03:00Z","op":"activate","from":"7247d80c7af7ffa70fc61557406bf491cdf3bfc7","to":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","backup":"20261022T040256Z-86f95bb27f6c","activation":"20261022T040256Z-86f95bb27f6c","result":"ok"}
{"at":"2026-10-22T15:05:55Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":null,"activation":"20261022T150555Z-84f53540dd0d","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
Console: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
result: pass
bus status:
host: mosaic-stack, pid 1791120, running, since 2026-10-22T20:16:23.031Z, notifier off
socket: present
writer.lock: pid 1791164
boot: organization file loaded at 2026-10-22T20:16:23.030Z
unit flags: match the pointer
drift: 94 commit(s) on refactor since the pointer's copy
# exit 0, end 2026-10-23T03:41:42.847Z
## P1 values
# start 2026-10-23T03:41:42.849Z
# command: (cd $REPO && <clean env> node /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/p1.mjs)
bus.tester true
plugins kinds ["channel","harness","usage-source"]
plugins usage-source ["transcript"]
plugins channel ["cli","webui"]
plugins harness ["claude-code"]
launch.by "chief"
launch.instances includes assistant true
role assistant vars.harness "claude-code"
role chief vars.harness "claude-code"
role coder vars.harness "claude-code"
role cto vars.harness "claude-code"
role pm vars.harness "claude-code"
role reviewer vars.harness "claude-code"
roles naming pi 0
# exit 0, end 2026-10-23T03:41:42.876Z
## P1 business-check OLD
# start 2026-10-23T03:41:42.900Z
# command: (cd $REPO && <clean env> node /mnt/storage/src/mosaic-stack/agents/sage/work/window-plan/repin-de352dfaf/business-check.mjs 0cbe2e9b35e51fe06dedde69c022b46e040f98c0)
copy 0cbe2e9b3: roles 6, projects 1, arbiters 2
HOLDS chief instance.name yes
HOLDS pm instance.name no
LOAD_OK
# exit 0, end 2026-10-23T03:41:42.939Z
## P1 config.json top-level key names (C1; names only)
configVersion dataRoot environment execution
# C1: none of federation, compactionReadBytes, compactionStreams; console key absent
## P1 data console
# dataRoot /home/jwoltje/.mosaic-dev
# ls -ld $DATA/console:
drwx------ 2 jwoltje jwoltje 4096 Oct 22 15:16 /home/jwoltje/.mosaic-dev/console
# ls rc=0
# tester.sock path length 45 bytes (limit 107)
# P1 end 2026-10-23T03:41:42.993Z
@@ -0,0 +1,6 @@
# P4 2026-10-23T03:42:14Z
DropInPaths=
ExecStart={ path=/home/jwoltje/.local/bin/chisel ; argv[]=/home/jwoltje/.local/bin/chisel client --fingerprint ce+ISFvMAhuoCRDuc7riIf+hYz87TLHk/C9C/H8QsUg= --keepalive 25s --max-retry-interval 10s http://10.1.1.227:8080 R:0.0.0.0:7332:127.0.0.1:7332 ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
# exit 0
# R: specs (1): R:0.0.0.0:7332:127.0.0.1:7332
P4 PASS: one R: spec R:0.0.0.0:7332:127.0.0.1:7332, DropInPaths empty
@@ -0,0 +1,8 @@
# P5 2026-10-23T03:42:14Z; HEAD 464010fbe4c6925b077b9b8182a209ee2177eb9d
# git diff --quiet 464010fbe4c6925b077b9b8182a209ee2177eb9d HEAD -- scripts/stack-release.sh scripts/webui-service.sh scripts/bus-service.sh scripts/stack-copy.sh: exit 0
# git status --porcelain (exit 0): []
# scripts/stack-release.sh HEAD blob 7a8c347b2be483c8c2d25a2b75b910ae615319a6 NEW blob 7a8c347b2be483c8c2d25a2b75b910ae615319a6
# scripts/webui-service.sh HEAD blob 1e4ca9681f4388cccf9964f8492de030209bbd2f NEW blob 1e4ca9681f4388cccf9964f8492de030209bbd2f
# scripts/bus-service.sh HEAD blob 0b1d681f3515eceefea89d6b77e048c444e79a6e NEW blob 0b1d681f3515eceefea89d6b77e048c444e79a6e
# scripts/stack-copy.sh HEAD blob 1b0d76daf1c59c1d875fc845bc24ddd0bd51131c NEW blob 1b0d76daf1c59c1d875fc845bc24ddd0bd51131c
P5 PASS
@@ -0,0 +1,193 @@
# Migrate window 0cbe2e9b3 to the refactor tip (rows 270, 271, 203, 207, case fixes), schema 3m to 3n
Darkwing operates this window. Sage assigned it in sage-3135 on Mos's go mos-1385 (02:24:09Z 10-23, under
Q173). Mos's conditions: backup first, a STATE report after, and the new activation id and the Console 200 on
7332 in the report. The layout follows my last window's `window-migrate-0cbe2e9b3/PLAN.md`. Where a step is
unchanged I name it and only say what differs. The window's files go on a darkwing/* branch, never local
refactor.
## Who, and what nobody does
- I operate. Mos checks each step's output before the next one, runs P3 and gives the W5 go. Sage gives the
slot gap: after Gander's 574 runs (row 293), before Rocko's 219 runs. Until Sage's CLEAR, only plan text, git
reads and file prep. D3 and BZ are plain tar, cp and sha256sum and may run before the gap. B0 is python3 -B and
runs in the gap.
- Jason has no step. Nothing gets pruned. Nobody acts as Jason: no `--by jason`, no human shell or tmux pane,
every bus read carries `--tester`, and `bus.tester` stays as it is. If it isn't `true`, stop at P1.
- After W5, any Console start or restart is Mos's. I don't start, stop or restart the Console, the bus or
mosaic-webui by hand.
- Nothing new goes on: no `mosaic access` (row 207's `user add|remove` included), no Gitea grant run, no
`--send`, no `--sso`, no proof pair, no pi (#383), no federation key. dragon-lin only; .53 and .54 take
nothing.
- Decision 96 condition 2: NEW's `--plan` prints `needs: Jason present for this run (decision 96 condition 2)`
(`scripts/stack-release.sh:1413` at NEW). Q173 overrides it for this business, as at the last three windows.
The line is expected in W3 and doesn't stop the window.
## Names
- OLD, live: `0cbe2e9b35e51fe06dedde69c022b46e040f98c0`, schema 3m, pointer `pm: true`, activation
`20261022T201621Z-0cbe2e9b35e5`, standing chief run `r148e72c8895d` (launched by jason at 20:16:24Z), no pm.
The pointer's Console flags are `--port 7332 --public-host mosaic.woltje.com --auth-header
X-authentik-username --allow-user jason.woltje --tester-socket`. P1 reads them again from `status`; nothing
copies them from this file.
- BASE: `253346e3c3917d4c9c0f79bc87b7db6aca73180d`, the tip when sage-3135 was sent. 88 commits past OLD.
- NEW: the refactor tip at window start, written to `$WIN/NEW` by P0 (git reads only). P0 refuses unless OLD
and BASE are ancestors, every path BASE..NEW is `.md`, `docs/` or `agents/`, and NEW's stack-release.sh blob is
`7a8c347b2be4`. At 02:39Z the tip is `b23783f3951e5b750ef49cd0c2d7353640ea6899`, Sage's docs commit
(BUILD-LOG, SESSIONS, QUEUE, queue.json, a plan doc). Once P0 writes it, NEW is frozen for the window.
- `WIN=agents/darkwing/work/window-migrate-3n/`. `vars.sh` holds OLD, BASE, the tool blob and the four
service scripts, and reads NEW and this file's sha256 from P0's files. Every step script sources it and is
mode 755. `p0.sh` and `w9b.sh` are new; `w5pre.sh` is written again for this window's chief; the rest adapt
the last window's.
## What NEW carries over OLD
The non-test paths changed from OLD to BASE (`git diff --name-only`), without tests, agents/, docs/ and `.md`.
This covers the 276/203/207 re-pin that sage-2989 planned.
| Row | Commits | Paths | Live effect |
|---|---|---|---|
| 271 (264d, #1776) | a9884dc75, aa8fc490f, 3fc8ccef1 | packages/bus/schema.sql, src/migrations/3m-3n.sql, src/store.mjs, src/broker.mjs, src/process.mjs, src/runtime.mjs, packages/cli/src/launcher.mjs, scripts/stack-release.sh | Schema 3n adds `session_ops` and `message_cancels` (both AUTOINCREMENT, STRICT), index `session_ops_session` and 7 append-only triggers. No existing row changes. The broker gets `session.clear`, `session.cancel` and `session.operation`; no CLI verb at NEW reaches them, and only they write the two tables (the INSERTs at broker.mjs:668, 718 and 6467). stack-release.sh's COUNTED adds both tables. |
| 270 (264c, #1775) | 5ddc36ca9, bce491715, 8c28ee731 | packages/seat/src/compactions.mjs, packages/cli/src/launcher.mjs, packages/bus/src/broker.mjs, runtime.mjs, scripts/mosaic-config.mjs | The launcher appends to `<store dir>/compactions.jsonl` at the start and end of a thread-session run (`launcher.mjs:507, 648`). A launch with no thread has no store and writes none. For a claude-code run the count is unknown, `harness-unsupported`. Two new optional system config keys, `compactionReadBytes` and `compactionStreams`. |
| 203 (FED R2, #1701) | 93a491d73, 284d55cf2, 7c543fd4a | scripts/mosaic-config.mjs, scripts/stack-release.sh, scripts/test-config.sh | An optional `federation` key in the system config, and stack-release.sh's config_checks now run on a console or a federation key. The live config has no federation key (P1 checks). |
| 207 (#1705) | 721268b08 | packages/cli/src/access.mjs | `mosaic access user add|remove`. Nothing in the window runs it. |
| case fixes | b96b4e303, 537ce2dd4, 47906e4fd, 931fab079 | scripts/case1-accept.sh, scripts/case2-unit.sh | Case-run harness fixes. Nothing live runs these on dragon-lin. |
bus-service.sh, webui-service.sh, discord-service.sh, stack-copy.sh, common.sh, package.json and
package-lock.json haven't changed from OLD (`git diff --stat` empty). packages/seat/src/session.mjs is the same
blob at OLD and NEW. packages/tasks/requirements.json is still PRD 1.15 with 85 requirements.
stack-release.sh changed only in COUNTED (`:420`) and config_checks, so W3's line formats are the last window's:
`store schema:`, `target schema:`, `migration: X to Y, after a backup of the stopped store`, `migration steps:`,
`needs: --migrate (given)`, the Jason line, and `commits: the run adds N`.
### Config keys (C1)
OLD's loader takes only configVersion, environment, dataRoot, execution and console (`mosaic-config.mjs:246` at
OLD). NEW's adds federation, compactionReadBytes and compactionStreams (`:304`). OLD runs on the live config now,
so none of the three can be there. P1 prints the live config's top-level key names (names only, no values) and
stops on any of the three. W0 runs NEW's `mosaic-config.mjs validate` from the NEW copy and needs exit 0.
## Schema and backups
- `SCHEMA_VERSION` is 3n at NEW (`packages/bus/src/store.mjs:17`) and 3m at OLD and in the live store. The chain
runs one step, 3m to 3n. W3 must show `migration: 3m to 3n, after a backup of the stopped store`,
`migration steps: 3m to 3n`, `needs: --migrate (given)` and the Jason line. Any other migration text stops W3.
- Counts. The live 3m store has 24 rows of type `table` in sqlite_master (sqlite_sequence included), 90
triggers, 5 views and 25 indexes (the last window's W6 and P3). 3m-3n.sql adds 2 tables, 7 triggers and 3
indexes: `session_ops_session` plus one autoindex for each UNIQUE (`session_ops (thread, op, stage,
attempt)`, `message_cancels.message`). So W6 expects 26, 97, 5 and 28. schema.test pins `count('table') - 1`
at 25 and triggers at 96 (`packages/bus/tests/schema.test.mjs:527-528`) after it drops
`task_snapshots_no_update` at `:512`, which agrees: 26 - 1 and 97 - 1. 96 on live would mean a trigger is
missing.
- The migration writes no row, so sqlite_sequence doesn't change. Both new tables stay at 0 rows in the window,
since nobody runs `session.clear` or `session.cancel`.
- Restore guard at NEW counts events plus 16 tables (`COUNTED`, `stack-release.sh:420`); session_ops and
message_cancels are the new ones. T45's LISTED and T46 cover them. The gate run 5 GREEN on 53406f3e4
(gizmoduck, 7328da8ca) ran on the same code tree as NEW.
- D3 (`d3.sh`): the data-root tar, same exclusions as last time (backups/, the live store and its -wal and -shm,
sockets, `discord/searxng`, `launches/*/*/tmp`). It now expects `bus-host/sessions.json` at sessionsVersion 2,
which it has been since the last window. Plain tar; may run before the gap.
- BZ (`bz.sh`): the business file copy, sha256 must be 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93
as at the last two windows. cp and sha256sum only; never read.
- B0 (`b0.sh 3m`), in Sage's gap: the online sqlite backup-API copy laid out for `rehearse --backup PATH`.
`SCHEMA` must say 3m. Its path and SHA256SUMS sha256 go to Mos with D3's before P3. This is Mos's "backup
first".
- W5 takes the stopped-store backup every migrating activate takes, before the migration. That backup is the
store's rollback point. If W5's gate fails after the migration, the in-run rollback restores it by itself
(`TAKEN`), which still counts as a restore and ends the window.
- Disk: /home has 36G free. The data root is 2.0G without backups/ and rehearsals/, 6.7G with them. The last
D3 is 1.5G.
## Before the window (nothing stopped)
| # | Who | Step |
|---|-----|------|
| P0 | operator, git only | `p0.sh <this file's sha256>`: writes `$WIN/NEW` and `$WIN/PLAN.sha256` (see Names). NEW's full sha and this file's sha256 go to Mos. |
| D3 | operator | `d3.sh`, above. |
| BZ | operator | `bz.sh`, above. |
| B0 | operator, in Sage's gap | `b0.sh 3m`, above. |
| P1 | operator | `p1.sh`: `status` shows pointer OLD, activation `20261022T201621Z-0cbe2e9b35e5`, 3m, the Console flags, unit flags match. Business file sha256 1e49bdf0 and `business-check.mjs` LOAD_OK for OLD. `$DATA/console` 0700, `tester.sock` 0600, `bus.tester` `true`. C1's key names. |
| W0 | operator | `w0.sh`: `scripts/stack-copy.sh copy $NEW`, `business-check.mjs` LOAD_OK for NEW, and NEW's `mosaic-config.mjs validate` exit 0. |
| P2 | operator, in Sage's gap | `p2.sh` and `p2units.sh`: the two stub runs against NEW's copy, then both units rendered from REPO's service scripts and diffed against the live units. The copy path is the only difference allowed. |
| P3 | Mos, in Sage's gap | `stack-release.sh rehearse $NEW mosaic-stack --backup <B0 path> --port 7341`, no `--migrate` (`:138` accepts it for activate only; rehearse migrates by itself when the schemas differ). Expect `PASS new-refuses-old-store`, `PASS migrate: 3m to 3n`, `PASS new-opens-migrated`, `PASS migrate-again`, `PASS old-refuses-migrated` (0cbe2e9b3's copy refusing the 3n store), `PASS gate`, `PASS no-session`, `REHEARSAL_PASS`. Then `python3 -B p3/counts.py <B0>/bus/bus.sqlite <rehearsal>/data/bus/bus.sqlite`: every 3m table equal, session_ops and message_cancels `None -> 0`, sqlite_sequence equal. `p3/hand-console.sh <rehearsal dir>` if Mos wants the hand Console read again; it now takes the directory as its argument and refuses one that isn't NEW's. |
| P4 | operator | `p456.sh p4`: the tunnel read. |
| P5 | operator | `p456.sh p5`: the four service scripts held at NEW with empty porcelain. |
| P6 | operator | `p456.sh p6` as `matrix-old.txt` and `p456.sh tester` as `tester-old.txt`: the 7332 matrix (a)-(g) and the two tester-http reads, for W10. |
## The window
| # | Who | Step |
|---|-----|------|
| W0b | operator | `wsteps.sh` runs W0b to W4 in one go and stops at the first failed check. W0b is the business file copy again. |
| W1 | operator | Idle check (`agents --tester`, `launches list`), start stamp from `date -u`, S1 with window-repin-84f53540d's snapshot.mjs at 639fd28a, `--held`. The only live launch allowed is the standing chief r148e72c8895d; any other, a pm included, stops and goes to Mos. Then `d3.sh` again, so the rollback source is as of just before W5. |
| W2 | operator | `status`: pointer OLD, `pm: true`, 3m, unit flags match. |
| W3 | operator | `stack-release.sh activate $NEW mosaic-stack --pm --migrate --tester-socket --plan`. It must show OLD to NEW, the migration lines above, the backup line, and a Console line with exactly OLD's flags: `--tester-socket`, no `--send`, no `--sso`, no proof pair, no drop line. Any other difference stops here. |
| W4 | operator | The unit reads. W3 and W4 go to Mos for the W5 go. |
| W5 | operator, on Mos's go | `w5pre.sh` right before: agents and launches again with W1's rule; chief or pm working stops. Then `w5.sh`: the same activate without `--plan`. Exit 0, `activate ok`, `gate passed`, `stopped store schema: 3m`, `migrated the store to 3n`, no drop line. Record the activation id, backup path, STAMP and the backup's SHA256SUMS sha256, and the bus journal's `launched` lines since W5's start. Then `status` (NEW, pm true, 3n, check pass) and `rollback mosaic-stack --restore <W5 activation> --plan`, read only, for the guard count. |
| W6 | operator, after Mos checks W5 | `w6.sh`: `status` shows 3n; a read-only python3 count on the live store gives 26 tables, 97 triggers, 5 views and 28 indexes, schema_version 3n, every 3n object by name, 0 rows in both new tables; `agents --tester` answers; `tr.sh` thread reads match W1's (ids, message counts, sha256); `bus-host/sessions.json` at sessionsVersion 2. |
| W7 | Mos | Any Console restart. I record the Console pid and `/proc/<pid>/cwd` before and after, and change nothing. |
| W9 | operator | `w9.sh`: the last window's W9 list, with the tester pm read dropped (mos-1344 carry 1): the Console unit active, started after W5, cwd NEW's copy (mos-1338 item 4); status 3n; console-check d068fe02 CONSOLE_PASS; webui journal since W5 with one `proxy proof off` and no `Sign-in:`; `tester.sock` 0600 in a 0700 `console/`; one peercred-starts line naming NEW; no `library:` line; one running chief from this start; no pm launch; the chief not working. |
| W9b | operator, ruled in (mos-1392) | `w9b.sh`, the pm launch proof, under the five conditions below. |
| W10 | operator | `w10.sh`: the matrix again as `matrix-new.txt`, lines a-g the same as `matrix-old.txt` (line a is the Console 200 on 7332, `healthz 200 ok=true`); the two tester-http reads (the `/api/config` line identical, `/api/bus/agents` 200 with its count shown beside P6's count from `tester-old.txt`, mos-1344 carry 3); no listener on 7333; P4 again; Console POSTs on 7332 with no session refused (mos-1195); tester `POST /api/processing` refused 403 read-only. |
| W11 | operator | STATE in `$WIN/STATE.md` with the activation id, W10's line a and W9b's pm marked "left live by W9b"; comments on #1776 and #1775 (`post-gitea.sh 1776 1775`, as darkwing, read back); the P19 body to Sage, with the same pm lines; the window's files and this plan revision committed and pushed to a darkwing/* branch. |
### W9b, the pm launch proof (ruled in, mos-1392; mos-1344 carry 2)
No pm is live on OLD, so the window has no pm to read. P3 can't prove a pm launch either: rehearse runs its host
with no launches and checks `no-session` (no sessions.json, no launches under the rehearsal data root). A pm
launch on NEW can only be proved on live, and that means writing chief and pm turns, messages and a launch.
The step is the last re-pin's W12c step 1 (window-repin-84f53540d/W.txt, 15:23Z 10-22), without row 66's turns:
1. Backup first: `b0.sh 3n`, an online copy of the held 3n store.
2. One relay: `talk chief "[mos test] Please relay this to the pm: [mos test] read packages/tasks/requirements.json through your requirements read and reply with the PRD version and the number of ids." --wait 300 --business mosaic-stack --tester`.
3. `launches list --json` within 120 s: exactly one live pm, `launchedBy` chief, `startedAt` after the relay.
Its outer pid or `runnerPid` must name NEW's copy in `/proc/<pid>/cmdline` or cwd.
4. One read: `talk pm "[mos test] read packages/tasks/requirements.json ... the version and the number of ids" --wait 300 --tester`, expecting 1.15 and 85.
Mos's conditions (mos-1392, through sage-3151), which `w9b.sh` follows:
1. `b0.sh 3n` runs first, and its online copy has to verify before the relay goes out: `B0_PASS`, which needs the
copy's integrity_check `ok` and `sha256sum -c` on its SHA256SUMS.
2. Only the NEW-copy match is recorded from `/proc/<pid>/cmdline` or cwd: a count of argv entries naming the
copy and a yes or no for the cwd. Never argv itself, never the environment, nothing that holds a `--setenv`
value.
3. The pm's run id, pid and runner pid go into STATE.md and the P19 body, marked "left live by W9b". The launch
log has no launch id apart from the run id: `launches list` keys each launch by `run`, so the run id stands
for both. The next window's W1 has to expect that pm.
4. W9b never runs while 574 is in a run phase. Sage's order puts 574 run 2 before my slot. If something
reorders, `w9b.sh` holds: `sage-busy-now.sh` must print CLEAR first, or the script exits with `W9B_HOLD`,
writes nothing to the bus, and waits until 574 finishes or stops.
5. The first failure stops the step and goes to Mos and Sage. Failures are a relay timeout or failed relay, a
live pm count other than one, `launchedBy` not chief, `startedAt` before the relay, no NEW match, or a read
that doesn't return 1.15 and 85. If the store or the pointer is in doubt after a failure, restore per the
rollback section below. A pm that launched and then failed its read stays live and goes in STATE as left
live by W9b; that alone isn't a reason to restore.
What that costs:
- The pm stays live into later windows.
- Both messages carry `[mos test]` (Q134 C).
- The relay is a launch without a thread, so it writes no `compactions.jsonl` (see row 270 above).
## Steps skipped, and why
- W8 (the last window's A6 share check) isn't needed: NEW adds no share path. 206's share verbs are unchanged
and already on live.
- Row 66's live check isn't run: it isn't in mos-1385, and it writes chief turns.
- No thread create or launch, so neither 271's clear and cancel verbs nor 270's ledger are exercised on live.
271's verbs have no CLI verb at NEW. Both rows' tests ran green in gate run 5 on 53406f3e4.
## Stop conditions and rollback
- The first failure stops the step and goes to Mos. Any restore, or a second failure, ends the window. Before W5
there's nothing to undo. Keep every copy and prune nothing.
- After W5, a pointer flip alone can't roll back: 0cbe2e9b3's copy refuses a 3n store (P3's
`old-refuses-migrated`). Rollback is restore-only: the store from W5's stopped-store backup, the data-root
files from W1's D3, and a re-pin to 0cbe2e9b3. It goes to Mos for the go, with `rollback --plan`'s guard count
and the event types after the backup. Q173 overrides decision 96 condition 3, as before.
- The last window's sessions.json hazard is gone. OLD and NEW have the same session.mjs, and both read
sessionsVersion 2. A `compactions.jsonl` that a later thread launch writes on NEW is a file OLD doesn't read,
so it stays where it is.
- Before any real restore I take an online copy of the 3n store with `b0.sh 3n`, so the rows the restore drops
are kept. Its path and SHA256SUMS sha256 go to Mos with the request.
@@ -0,0 +1 @@
e664fa3553f9da0bb29905763011223e69c426a56e484ecfd8181743b7628ad1
@@ -0,0 +1,20 @@
# Rulings on PLAN.md (sha256 af52429480de6629de4c8f8313d11fd875723566d8db142d2b7e67c4b04c17ac)
PLAN.md is left as sent, so its sha256 stays the one Sage passed to Mos. These rulings override it where they differ.
sage-3150 (02:42:54Z 10-23), plan accepted apart from W9b:
1. D3 and BZ don't run early. Gander's 574 run 2 (from 02:40:35Z) has a load-sensitive band (case1-accept P10,
Vikunja 573). They run at the start of my slot, before B0.
2. BASE..NEW stays docs-only, since 574 lands after W11. P0 writes NEW, and that sha holds for the whole window
even if a doc commit lands during it.
3. Order: Gander's run 2, then Sage's claims gap, then the CLEAR, which comes with Mos's W9b ruling (sage-3149),
then the window.
W9b: Sage recommended ruling it in, with the pm's run id recorded in STATE.md as left live. Mos rules.
W11: commit and push the window files to a darkwing/* branch, as planned.
sage-3151 (02:45:59Z 10-23): Mos ruled W9b in (mos-1392, 02:45:35Z) with five conditions and asked for them in
the plan before the slot. PLAN.md is revised for that one change: the W9b row, the W9b section (conditions 1-5)
and the W11 row (the pm left live, and the plan revision committed with the window files). Its sha256 changes
from af524294...c17ac; P0 takes the new one. w9b.sh follows the conditions: a CLEAR hold first (W9B_HOLD, exit 2),
B0_PASS before the relay, match counts only, $WIN/W9B-PM.txt with the pm's run id, pid and runner pid as left
live by W9b, and exit 1 on each named failure, the read miss included.
@@ -0,0 +1,269 @@
{
"schema": "3m",
"tables": {
"decision_events": {
"rows": 22,
"maxSeq": 22,
"sha256": "b016d2a7cb0116252166016822c472af1cc520435a6538ecb7be5db62c6cb4b3"
},
"decisions": {
"rows": 22,
"maxSeq": 22,
"sha256": "eafcd57d3531f7ac7c768e512632a3975df372c49ffd95abe7d858fd05ce93a8"
},
"deliveries": {
"rows": 1935,
"maxSeq": 1935,
"sha256": "42f92c45a38f7b7fbeb8871dd78a4b963ad902c3fda93bd7c62ddb8881242c80"
},
"events": {
"rows": 2200,
"maxSeq": 2200,
"sha256": "e7e46574bd7b737dd283c67d2bf6c3918b888c6de4cacd42a60f1f0e78a1b9a7"
},
"gitea_grants": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"gitea_users": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"host_grants": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"hosts": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 10,
"maxSeq": null
},
"messages": {
"rows": 635,
"maxSeq": 635
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "e2dbed747bf81f25f6aa3f50c9a456110d536582be1933e79ddc35896658b218"
},
"projects": {
"rows": 5,
"maxSeq": 5,
"sha256": "cdf87b72ef95a2f988c242d35f1050f9e3e310637f84a645b51694daa924650f"
},
"role_claims": {
"rows": 115,
"maxSeq": 115,
"sha256": "6e16043dbdffa2c6b1c18b43151ecac01787a94ca77c74e60a4e66ae09e803ee"
},
"session_defaults": {
"rows": 5,
"maxSeq": 5,
"sha256": "0fa4a532b106e963e200064d7f7464412a2fd6fce5a59a35ba7097d9bdca879c"
},
"sessions": {
"rows": 5,
"maxSeq": 5,
"sha256": "7094392718785cbaec3d5e5bd640f4b36d1e2be1fb4b7c6a9db77308de111803"
},
"task_comment_cursors": {
"rows": 666,
"maxSeq": 666,
"sha256": "827456a5efbac1c1ddc455867a7896c33a4073930f8bb7911182713d12fb8ce1"
},
"task_snapshots": {
"rows": 577,
"maxSeq": 577,
"sha256": "99f2aefde907be0142d8e173276add60083eae75851c11e85e9fbdd30af73386"
},
"thread_claims": {
"rows": 10,
"maxSeq": 10,
"sha256": "925e82e219ca7d469a3d9d72bd7033b9f52a47f1da272ac7ae31832a24cd1b91"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"thread_shares": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 5,
"maxSeq": 5,
"sha256": "6cc7fbd14dd1d8164dbeab1e2c2911ca206c283e722724ee2b0b0a8e22e0f533"
},
"usage_records": {
"rows": 104,
"maxSeq": 104,
"sha256": "12e30281ae16ee8536c7d4ccdff50f2c3da441504b5b17810bcb0031bcca36f3",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r34520a581318",
"input": "6",
"output": "323",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7e99838f9f93",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc964611b2092",
"input": "4",
"output": "120",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rbb708846b605",
"input": "12",
"output": "2089",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc36b17ef3788",
"input": "16",
"output": "5221",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r097436c91816",
"input": "16",
"output": "3340",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r497f1c817d4d",
"input": "2",
"output": "80",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc9e993da24cf",
"input": "30",
"output": "5871",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7646a7a4a2bd",
"input": "12",
"output": "1448",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r4004e5e09433",
"input": "2",
"output": "61",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rd0b8fdb4d62d",
"input": "18",
"output": "1714",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r62b31aae5852",
"input": "2",
"output": "31",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r2cf8536da2c3",
"input": "12",
"output": "1420",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "re6c69f1ec1f0",
"input": "16",
"output": "1471",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r2c62e02f8371",
"input": "2",
"output": "68",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r410293d04d3d",
"input": "10",
"output": "1105",
"unknownTurns": 0
}
]
}
},
"untimed": [
"gitea_grants",
"gitea_users",
"host_grants",
"hosts",
"message_threads",
"meta",
"task_comment_cursors"
]
}
@@ -0,0 +1,137 @@
# Migrate window 0cbe2e9b3 to 464010fbe (rows 270 and 271, #1775 and #1776), schema 3m to 3n, STATE report (W11)
Operator and tester: Darkwing. Plan: `PLAN.md` in this directory, sha256
e664fa3553f9da0bb29905763011223e69c426a56e484ecfd8181743b7628ad1 (the sage-3151 revision for W9b; the sage-3150 and
sage-3151 rulings are in `RULINGS.md`). Tool blob 7a8c347b2be4 (`scripts/stack-release.sh` at NEW); P5 found the four
release scripts the same at HEAD and NEW. OLD 0cbe2e9b35e51fe06dedde69c022b46e040f98c0, NEW
464010fbe4c6925b077b9b8182a209ee2177eb9d, 94 commits past OLD, 6 past BASE 253346e3c with no runtime path changed.
Every stamp below comes from `date -u` or from the transcript `W.txt` in this directory. dragon-lin only.
Sage assigned the window (sage-3135) on Mos's go (mos-1385), ruled the plan (sage-3150, sage-3151) and gave the slot
(sage-3172, 03:39:53Z). Mos's gos and rulings: mos-1392 and mos-1394 (W9b in, five conditions), mos-1403 (P3 PASS, the
W0b to W4 go), mos-1404 (the W5 go), mos-1405 (W5 checked, the W6, W9, W9b, W10 and W11 go, no W7). Q173 covers every
step.
## Outcome
NEW is live for mosaic-stack and the store is 3n. W5's activate stopped the units, backed up the stopped 3m store,
migrated it to 3n in one step and started both units on NEW. Every check from P0 to W10 passed. Nothing was rolled
back or restored, and nothing was pruned.
- The live 3n store has 26 table rows, 97 triggers, 5 views and 28 indexes (W6). session_ops, message_cancels, the
index session_ops_session and the seven new triggers exist; session_ops and message_cancels hold 0 rows. NEW's
COUNTED list (`stack-release.sh:420`) names 16 tables, session_ops and message_cancels included.
- The five tester threads read the same after W5 as before it: the same ids, message counts and sha256 of each
`thread read --json --tester` (W1 against W6).
- bus-host/sessions.json was already sessionsVersion 2 before the window. NEW's host rewrote it at 03:46:01Z, still
v2 with 1 session.
- The Console started at 03:45:59Z, after W5.start, with its cwd in NEW's copy, so nobody restarted it (W7 not run,
mos-1405).
- The standing chief r148e72c8895d ended with W5's restart while not working. The new standing chief is
r28dbbc5c6b4d, launched by the new host (the bus journal's one launch line since W5.start).
- W9b proved a pm launch on NEW, which the last window couldn't (mos-1344). The relay through the chief launched pm
run ra38826f3c6e2, and its requirements read answered "Version 1.15, 85 ids". The pm is left live (below).
Nobody acted as Jason: no `--by jason`, no human pane, no auth header as jason, no pi. Nothing new went on: no
`--send`, no `--sso`, no proof pair, no grant. W8 and W12 were not run.
## pm left live by W9b
pm run ra38826f3c6e2 (claude-code), launchedBy chief, startedAt 2026-10-23T03:47:35.835Z, pid 3831373, runner pid
3831374: left live by W9b.
The run id stands in for the launch id, since `launches list` keys a launch by its run and has no other id. The next
window's W1 has to expect this pm, live or ended, beside the chief r28dbbc5c6b4d (mos-1392 condition 3). At W10 both
were idle.
W9b under the mos-1392 conditions:
1. b0.sh 3n ran first: `backups/b0-20261023T034725Z`, CHECK ok events 2202 maxSeq 2202 schema 3n, SHA256SUMS 0946b66c,
B0_PASS at 03:47:25.308Z, before the relay at 03:47:25.313Z.
2. Only the NEW-copy match is recorded: pid 3831373 has 37 argv entries naming NEW's copy, pid 3831374 has 1. Neither
cwd is NEW's copy. No argv text, environment or `--setenv` value is recorded.
3. This file and the P19 body name the run id and pids above.
4. No 574 run phase was open (sage-3172); w9b.sh checked `sage-busy-now.sh` for CLEAR first.
5. Nothing failed, so nothing was stopped or restored.
The chief's reply: it passed the request on as message 3479e027, found no pm session, launched one and saw nothing
refused. The pm's reply to the direct read: "Version 1.15, 85 ids (checked 10 seconds ago with list_requirements and
against packages/tasks/requirements.json at 464010f)."
## Deviation
1. `wsteps.sh` checks for `w0.txt`; I had written W0's output as `W0.txt`. I renamed it (`mv -n`) before wsteps ran.
Its content is unchanged.
2. My first `w5.sh` call left out the go argument, and the script refused on usage (`usage: w5.sh GO`) before it ran
anything or wrote a file. The pointer stayed on OLD. I ran the pre-read again (03:45:57Z, the second pre-read in
`W.txt`) and then `w5.sh mos-1404`. Mos accepted it (mos-1405).
3. W10's `/api/bus/agents` read 2 agents where P6 read 1. The second is the pm W9b left live: the two rows are chief
r28dbbc5c6b4d and pm ra38826f3c6e2, both idle.
4. W11's first two `post-gitea.sh` runs, launched from `/`, stopped at the login check with nothing posted:
`gitea-api.sh` needs a git working directory and failed with "not a git repository". For the second run I had
changed the login check on a wrong guess about pipefail; I reverted it, so the script is back at 47a24e05, then
ran it from the repo. Both outputs are kept (`post-gitea-try1.out`, `post-gitea-try2.out`).
## Gitea
`post-gitea.sh 1776 1775` as darkwing (`post-gitea.out`): #1776 comment 31009 and #1775 comment 31010, each read
back equal to its `comments.md` body, 03:52:34Z and 03:52:35Z.
## Stamps and activation
| Step | Start (UTC) | Result |
| --- | --- | --- |
| P0 | 03:41:34Z | P0_PASS, NEW 464010fbe, plan e664fa35, 6 commits past BASE, no runtime paths (`p0.txt`) |
| D3 data-root snapshot (slot start) | 03:40:56Z | `backups/d3-20261023T034056Z`, 91612 members, tar exit 0, SHA256SUMS 6e5cc745 (`d3-slot.txt`) |
| BZ business file copy (slot start) | 03:41:29Z | `.pre-window-20261023T034129Z`, sha256 1e49bdf0 (`bz-slot.txt`) |
| B0 store backup (3m) | 03:41:34Z | `backups/b0-20261023T034134Z`, ok events 2200, maxSeq 2200, 3m, SHA256SUMS 6589030e (`b0-3m.txt`) |
| P1, W0, P2, P4, P5, P6 | 03:41:42Z to 03:42:14Z | all PASS; W0 copy made, LOAD_OK; P2 stubs and units PASS (`P1.txt`, `w0.txt`, `p2.txt`, `p2-units.txt`, `P4.txt`, `P5.txt`, `matrix-old.txt`, `tester-old.txt`) |
| P3 rehearsal (Mos) | 03:43:20Z | REHEARSAL_PASS rc 0, every table count equal, session_ops and message_cancels None to 0 (`p3/p3.out`, `p3/counts.out`, mos-1403) |
| W0b business file copy | 03:44:07Z | `.pre-window-20261023T034407Z`, sha256 1e49bdf0 |
| W1 launches, agents, S1 | 03:44:07Z | chief r148e72c8895d standing, no other launch; S1 7e4d46f9 |
| W1 tester thread reads | 03:44:08Z | 5 threads (`tr.sh W1`) |
| W1 D3 again | 03:44:12Z | `backups/d3-20261023T034412Z`, 91652 members, tar exit 0, sessions.json v2, SHA256SUMS eda5be89 |
| W2 status | 03:44:32Z | pointer OLD, pm true, 3m, check pass |
| W3 plan | 03:44:32Z | 3m to 3n in one step, needs `--migrate` (given), 94 commits, Console line OLD's flags exactly (`W3-plan.txt` 229888d8) |
| W4 unit read | 03:44:33Z | OLD units, DropInPaths empty, bus `--pm`, no quote or backslash |
| W5 pre-read (mos-1404) | 03:45:42Z, 03:45:57Z | chief not working, no pm, no other launch |
| W5 activate --pm --migrate --tester-socket | 03:45:57Z to 03:46:02Z | exit 0, stopped store 3m, migrated to 3n, gate passed, activate ok (`W5.txt`) |
| W5 status, restore plan | 03:46:02Z | pointer NEW, pm true, 3n, check pass; restore plan exit 0, reads back to 3m, no post-backup refusal |
| W6 | 03:46:51Z to 03:46:56Z | W6_PASS (`w6.out`, `W6-counts.txt`) |
| W9 checks | 03:47:16Z to 03:47:17Z | W9_CHECKS_PASS (`w9.out`) |
| W9b pm launch and read | 03:47:25Z to 03:47:51Z | W9B_PASS (`w9b.out`, `W9B-PM.txt`) |
| W10 | 03:48:46Z to 03:48:47Z | W10_PASS (`w10.out`, `matrix-new.txt`) |
Activation id 20261023T034557Z-464010fbe4c6. Pointer log (`state/stack-activation-log.jsonl`): started at 03:45:57Z
(backup null) and ok at 03:46:02Z (backup 20261023T034557Z-464010fbe4c6), the same two-line pattern as the last
window. W5's backup: `~/.mosaic-dev/backups/stack/20261023T034557Z-464010fbe4c6`, STAMP 2026-10-23T03:45:58.274Z,
SCHEMA 3m, SHA256SUMS sha256 480f5e556d02d3f079c07782af204c0a41e44fb4633064e1d7c302e473be1390. The bus journal since
W5.start has one launch line, `launched chief: run r28dbbc5c6b4d`.
## W6, W9 and W10 detail
- W6 store count, python3 -B with mode=ro: `table=26 trigger=97 view=5 index=28`, schema_version 3n, nothing missing,
session_ops 0 rows, message_cancels 0 rows, thread_shares 0 rows, events 2202, maxSeq 2202. Thread reads match W1
(5 threads). sessions.json sessionsVersion 2, 1 session.
- W9 Console: mosaic-webui active, MainPID 3811007, started 03:45:59Z, cwd NEW's copy. Bus active, pid 3811005, cwd
NEW's copy. console-check.sh (d068fe02) CONSOLE_PASS. webui journal since W5: proxy proof off 1, Sign-in 0.
`console/` 0700 and `tester.sock` 0600. peercred-starts since W5 naming NEW: 1. Bus journal `library:` lines: 0.
One running chief from this start, r28dbbc5c6b4d; no pm before W9b.
- W10: matrix-new.txt equals matrix-old.txt (lines a to g). Line a is `a 200 ok=true`, the healthz 200 on 7332. No
listener on 7333. The tester `/api/config` read is identical to P6. `/api/bus/agents` is 200 with 2 agents (chief
and the W9b pm). P4 PASS. Unsigned POSTs to `/api/seen`, `/api/reply` and `/api/processing` on 7332 get 401
"sign-in required", and the tester-socket POST `/api/processing` gets 403 "read-only", sent false.
## Rollback sources (not used)
OLD refuses a 3n store, so a rollback now is a restore, not a pointer flip (mos-1404). The sources: W5's backup above
(3m) for the store, W1's D3 `d3-20261023T034412Z` (SHA256SUMS eda5be89) for the rest of the data root, B0
`b0-20261023T034134Z` (3m), and the business file copies `.pre-window-20261023T034129Z` and
`.pre-window-20261023T034407Z`. W9b's B0 `b0-20261023T034725Z` is the 3n store after W6 and before the W9b talks.
A rollback goes to Mos first. Nothing is pruned.
## Scripts
`p0.sh` d038e4ed, `wsteps.sh` d64828cb, `w5pre.sh` e5e6cc34, `w5.sh` 34b9ac8d, `w6.sh` 37e0d76b, `w9.sh` cca90dd0,
`w9b.sh` e56a1c52, `w10.sh` f67bd5a8, `tr.sh` 7a56dc29, `d3.sh` b5e1ca55, `b0.sh` 41b1fdfe, `bz.sh` 0ef4b29a,
`post-gitea.sh` 47a24e05. Thread read bodies stay in `.tr/` and are not committed.
@@ -0,0 +1,885 @@
# W0b-W4 start 2026-10-23T03:44:07Z
# plan e664fa35, NEW 464010fbe4c6925b077b9b8182a209ee2177eb9d, tool blob 7a8c347b2be4, P5 four scripts at NEW and clean, business-check.mjs eece4d46: checked 2026-10-23T03:44:07Z
# W0 copy present, w0.txt LOAD_OK
# W0b: /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261023T034407Z, sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93, recorded in backups.txt
## W1 agents --tester
# start 2026-10-23T03:44:07.945Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic agents --business mosaic-stack --tester)
chief r148e72c8895d claude-code since 2026-10-22T20:16:24.301Z
# exit 0, end 2026-10-23T03:44:08.158Z
## W1 launches list
# start 2026-10-23T03:44:08.161Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic launches list)
r148e72c8895d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-22T20:16:24.291Z running
# exit 0, end 2026-10-23T03:44:08.272Z
# W1 launches: chief r148e72c8895d standing (OLD's activation), no other launch
# W1 start stamp 2026-10-23T03:44:08Z
# SNAP sha256 639fd28a checked before S1
## S1 snapshot --held
# start 2026-10-23T03:44:08.278Z
# command: (cd $REPO && <clean env> node /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-repin-84f53540d/snapshot.mjs --store /home/jwoltje/.mosaic-dev/bus/bus.sqlite --held)
{
"schema": "3m",
"tables": {
"decision_events": {
"rows": 22,
"maxSeq": 22,
"sha256": "b016d2a7cb0116252166016822c472af1cc520435a6538ecb7be5db62c6cb4b3"
},
"decisions": {
"rows": 22,
"maxSeq": 22,
"sha256": "eafcd57d3531f7ac7c768e512632a3975df372c49ffd95abe7d858fd05ce93a8"
},
"deliveries": {
"rows": 1935,
"maxSeq": 1935,
"sha256": "42f92c45a38f7b7fbeb8871dd78a4b963ad902c3fda93bd7c62ddb8881242c80"
},
"events": {
"rows": 2200,
"maxSeq": 2200,
"sha256": "e7e46574bd7b737dd283c67d2bf6c3918b888c6de4cacd42a60f1f0e78a1b9a7"
},
"gitea_grants": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"gitea_users": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"host_grants": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"hosts": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 10,
"maxSeq": null
},
"messages": {
"rows": 635,
"maxSeq": 635
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "e2dbed747bf81f25f6aa3f50c9a456110d536582be1933e79ddc35896658b218"
},
"projects": {
"rows": 5,
"maxSeq": 5,
"sha256": "cdf87b72ef95a2f988c242d35f1050f9e3e310637f84a645b51694daa924650f"
},
"role_claims": {
"rows": 115,
"maxSeq": 115,
"sha256": "6e16043dbdffa2c6b1c18b43151ecac01787a94ca77c74e60a4e66ae09e803ee"
},
"session_defaults": {
"rows": 5,
"maxSeq": 5,
"sha256": "0fa4a532b106e963e200064d7f7464412a2fd6fce5a59a35ba7097d9bdca879c"
},
"sessions": {
"rows": 5,
"maxSeq": 5,
"sha256": "7094392718785cbaec3d5e5bd640f4b36d1e2be1fb4b7c6a9db77308de111803"
},
"task_comment_cursors": {
"rows": 666,
"maxSeq": 666,
"sha256": "827456a5efbac1c1ddc455867a7896c33a4073930f8bb7911182713d12fb8ce1"
},
"task_snapshots": {
"rows": 577,
"maxSeq": 577,
"sha256": "99f2aefde907be0142d8e173276add60083eae75851c11e85e9fbdd30af73386"
},
"thread_claims": {
"rows": 10,
"maxSeq": 10,
"sha256": "925e82e219ca7d469a3d9d72bd7033b9f52a47f1da272ac7ae31832a24cd1b91"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"thread_shares": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 5,
"maxSeq": 5,
"sha256": "6cc7fbd14dd1d8164dbeab1e2c2911ca206c283e722724ee2b0b0a8e22e0f533"
},
"usage_records": {
"rows": 104,
"maxSeq": 104,
"sha256": "12e30281ae16ee8536c7d4ccdff50f2c3da441504b5b17810bcb0031bcca36f3",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r34520a581318",
"input": "6",
"output": "323",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7e99838f9f93",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc964611b2092",
"input": "4",
"output": "120",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rbb708846b605",
"input": "12",
"output": "2089",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc36b17ef3788",
"input": "16",
"output": "5221",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r097436c91816",
"input": "16",
"output": "3340",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r497f1c817d4d",
"input": "2",
"output": "80",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc9e993da24cf",
"input": "30",
"output": "5871",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7646a7a4a2bd",
"input": "12",
"output": "1448",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r4004e5e09433",
"input": "2",
"output": "61",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rd0b8fdb4d62d",
"input": "18",
"output": "1714",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r62b31aae5852",
"input": "2",
"output": "31",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r2cf8536da2c3",
"input": "12",
"output": "1420",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "re6c69f1ec1f0",
"input": "16",
"output": "1471",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r2c62e02f8371",
"input": "2",
"output": "68",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r410293d04d3d",
"input": "10",
"output": "1105",
"unknownTurns": 0
}
]
}
},
"untimed": [
"gitea_grants",
"gitea_users",
"host_grants",
"hosts",
"message_threads",
"meta",
"task_comment_cursors"
]
}
# exit 0, end 2026-10-23T03:44:08.335Z
# S1.json sha256 7e4d46f9bc094ae10f1c76dfaee54407068bf99f52f8e465f36bc0bbcd961e68
## W1 tester thread reads (tr.sh W1)
W1 threads --tester: 5 thread(s), threads.json sha256 07c154d75d929028
W1 thread d0ba7a66-aa3f-4918-9518-94132bce6b28 messages 2 sha256 985ac61ca9220f2f
W1 thread 446201de-e8b5-4df3-9356-d0d5422cc899 messages 2 sha256 7e7e9eeeaa8c1e09
W1 thread b89fcca4-6b82-48bd-9310-fb82c5ae8518 messages 2 sha256 f4e4916b9656a403
W1 thread d9bb7ff3-7a8b-4bb8-ba12-03ca094cee4e messages 2 sha256 728ff36f3e003c63
W1 thread b85a57fe-84f9-4785-a596-135af39e334a messages 2 sha256 727de0d9c809a41a
# exit 0
# W1 D3: /home/jwoltje/.mosaic-dev/backups/d3-20261023T034412Z, STAMP 2026-10-23T03:44:12.165Z, SHA256SUMS sha256 eda5be89461d50c49cc3c2f32a03f49d3cd6d3848371e9de2eed183cec7cb74d, members 91652 tar exit 0 sessions.json sessionsVersion 2
## W2 status
# start 2026-10-23T03:44:32.643Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh status mosaic-stack)
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3m","activation":"20261022T201621Z-0cbe2e9b35e5","at":"2026-10-22T20:16:24Z"}
log (last five lines):
{"at":"2026-10-22T04:03:00Z","op":"activate","from":"7247d80c7af7ffa70fc61557406bf491cdf3bfc7","to":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","backup":"20261022T040256Z-86f95bb27f6c","activation":"20261022T040256Z-86f95bb27f6c","result":"ok"}
{"at":"2026-10-22T15:05:55Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":null,"activation":"20261022T150555Z-84f53540dd0d","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
Console: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
result: pass
bus status:
host: mosaic-stack, pid 1791120, running, since 2026-10-22T20:16:23.031Z, notifier off
socket: present
writer.lock: pid 1791164
boot: organization file loaded at 2026-10-22T20:16:23.030Z
unit flags: match the pointer
drift: 94 commit(s) on refactor since the pointer's copy
# exit 0, end 2026-10-23T03:44:32.916Z
## W3 activate NEW --pm --migrate --tester-socket --plan
# start 2026-10-23T03:44:32.918Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh activate 464010fbe4c6925b077b9b8182a209ee2177eb9d mosaic-stack --pm --migrate --tester-socket --plan)
plan: activate for mosaic-stack
from: 0cbe2e9b35e51fe06dedde69c022b46e040f98c0
to: 464010fbe4c6925b077b9b8182a209ee2177eb9d
store schema: 3m (read from the store the running bus holds)
target schema: 3n
migration: 3m to 3n, after a backup of the stopped store
migration steps: 3m to 3n
needs: --migrate (given)
needs: Jason present for this run (decision 96 condition 2)
backup: the run backs up the stopped store to /home/jwoltje/.mosaic-dev/backups/stack/<activation>/ before any migration
commits: the run adds 94 (newest first, at most 200):
464010fbe queue: revs 3435-3436. Row 294 (#1800): Duckworth APPROVE c30998 (3435, candidate 0b3f9c9d). Row 272 (#1777): Scrooge CHANGES c31006 (3436, candidate 0c3dc2ed); queue only
e57bfde73 queue: revs 3407-3434. Row 293 (574, #1805): Gander in-review r1 (3407, failed pre-send 3408), Sage request c30981 (3409/3410), Filbert APPROVE c30982 (3411). Row 292 (#1797): assigned gizmoduck (mos-1387), claim 3413, in-review r1 3419 (3420 failed), Sage request c30992 (3425/3426), Duckworth APPROVE c30975 (3433). Row 294 (#1800): Launchpad claim 3418, in-review r1 3422 (3423 failed), Sage request c30993 (3427/3428). Row 295 (#1801): Morgana claim 3414, in-review r1 3416 (3417 failed), Sage request c30991 resolved (3421/3424), Dewey CHANGES c30994 (3431). Row 272 (#1777): Huey claim 3415, in-review r1 3429 (3430 failed), Sage request c30997 resolved (3432/3434); queue only
319d9f87e sessions: Filbert row 293 (Vikunja 574) APPROVE c30982 rev 3411, plus re-added S0 re-check and 574 static review lines; docs only
b23783f39 Rows 270+271 landing (7328da8ca), 571 landing, MVP rows 293-299: BUILD-LOG phase line and Sage SESSIONS line; docs only
e18ad2711 docs: define mixed-format chapter ordering for conversion
12f8c3089 queue: rows 293-299 briefed (revs 3399, 3401-3406, sage) and row 293 claimed by gander (rev 3400, gander-293-start); queue only
253346e3c queue: rows 293-299 added (574 #1805, FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 #1804, 573 #1806; mos-1371/1372), 208 after 294; 270 round 3 request c30929 and Duckworth APPROVE c30930; 270+271 done at 7328da8ca (mos-1351); queue only
7328da8ca gizmoduck: row 270 (264c, #1775) gate results: run 3 red on case1-accept P10 band (Vikunja 573) and run 4 red on the checkout.test fixture race (Vikunja 574), both old tests unchanged in the range; run 5 GREEN on 53406f3e4 under mos-1369 and sage-3106, all phases and 38/38 mutants killed; BUILD.md only
84002f960 gizmoduck: row 270 (264c, #1775) gate run 2 second red, fixed under mos-1364 and sage-3093: compactions-budget.test F1 (:39 input 8.5 * MiB is a whole number, now MiB + 0.5) and F2 (the 0o000 window in try/finally; the after-hook ran after setup's rm); K M22 :129, M23 :120, M22 and M15 notes; BUILD.md run 2
f89aaf57f gizmoduck: row 270 (264c, #1775) gate run 1 red placed (mos-1272): compactions-launch.test's host wires row 271's five clear ops, as session-clear.test does; the clear test's successor never started without them; kill lines +5 in mutants.py K; BUILD.md run 1
d3a49f301 gizmoduck: rows 270+271 combined gate (C2) record before the first run (sage-3073, sage-3086, mos-1275, mos-1351): blob table, timeout table, GREEN and RED, order; code is ac44d0de9's
562b05b43 gizmoduck: row 270 (264c, #1775) round 4 for sage-3073 and sage-3078: rows 270 and 271 rebased onto 25c09f19f (row 203 plus the O2 docs); mosaic-config.mjs keeps 203's federation key and both compaction keys; BUILD.md round 4 with the range-diff, the three merged files and C1 after 203; not run
8c28ee731 gizmoduck: row 270 (264c, #1775) round 3 for duckworth-0226 B4 (sage-3038): one collection opens at most the system config's compactionStreams stream files in name order (default 1024, 32 to 65536, a bad value takes the default and logs), not-collected past it with no cursor for an unopened stream; the missing-stream check uses a Set; readStreams, cap and launcher tests; mutants M22-M24, M10 anchor moved; docs/TOOLS.md has both keys and C1 (N4); BUILD.md round 3 with stream counts, the 231 KB bound, C1 and N5; not run
bce491715 gizmoduck: row 270 (264c, #1775) round 2 for duckworth-0223 B1-B3 (sage-3030, sage-3031, mos-1352, sage-3033): pi streams read in 1 MiB chunks under a budget from the system config's compactionReadBytes (default 64 MiB, 1 MiB to 1 GiB, a bad value takes the default and logs), not-collected past it; Claude history from before the ledger is harness-unsupported for good; launcher, runtime reader, clear and legacy tests; mutants M13-M21; BUILD.md round 2 with sizes, schema change, rollback hazard, N1-N3; not run
01b57f48b gizmoduck: row 270 (264c, #1775) round 1 for review (sage-3024): mutants.py M1-M12 with anchors at 7d199f033, each matching once, kills read from the tests; run plan with broker.mjs reruns; launcher and runtime wiring named as untested; not run
54ec0005e row 271 (264d, #1776): BUILD.md r3 gate on 9e18a9aaf (sage-3017): cli and bus session-clear.test whole green (5/5, 19/19), cli suite 874/874, M14 killed by the T6 test's first lines() assertion at :365; no first-run red. Doc only.
61e262b7c row 271 (264d, #1776): r3 packet carries mutants.py, M1-M14 with their anchors, as the gate applies them (duckworth-0212 C1). Doc and harness only; no src or test change.
540b6f16b row 271 (264d, #1776): r3, T6 per sage-3001. New cli test: a bus unavailable (broker-exited) at the exit proof, then at retirement, holds the clear with no retirement, successor launch or slot handed on, logs once, and resumes when the bus answers. BUILD.md maps the halt case to session-launch.test.mjs:372 and launcher.mjs:824, binds the Q198 hold to row 257 with duckworth-0210's carry, and names M14 (drop clearPass's try/catch) and its kill. No src change. Not run (no CLEAR).
e6a675ee8 row 271 (264d, #1776): BUILD.md gate results on e8af389e6 (sage-2991): 18 touched files, bus/seat/cli suites and every test-*.sh green, M1-M13 killed by assertions in the named tests; the row gate's T6 coverage open to Sage. Doc only.
e77635ceb row 271 (264d, #1776): BUILD.md run limits for the e567f949c gate (mos-1275, sage-2991); mutant lines moved one down past the B1 comment. Doc only.
3fc8ccef1 row 271 (264d, #1776): duckworth-0201 B1: #ownerRefusal passes 'session.clear' to #granted, so the project and instance sit in their own slots and a live collaborator grant passes the retirement recheck. N4 setup shared; a second N4 test keeps the grant and expects retired plus the successor; M13 restores the defect. 264e carry noted at the N4 tests. Not run (no CLEAR).
27d3266ff row 271 (264d, #1776): BUILD.md records mos-1336, Mos's approval under mos-1292 of the later3l, rows() and thread-shares migration test changes on the 3m to 3n rebase, with its conditions
6bd67225b row 271 (264d, #1776): BUILD.md states the post-rebase '3m' grep result as measured
14715d644 row 271 (264d, #1776): r2 on 0cbe2e9b3 (sage-2968): 3m to 3n migration and 3m engine tests from fixtures/schema-3m.sql; BUILD.md records the label change, the old-test changes (later3l, rows() and the thread-shares migration test outside the mos-1315 list, for Mos) and the mutant lines on the rebased tree
f9a9c9b5a row 271 (264d, #1776): BUILD.md defines M1-M12 with their expected kills
34388223d row 271 (264d, #1776): launcher uncertain-path test with test-driven ticks and a stopped runner; start() takes stopTimeoutMs
3adb391b5 row 271 (264d, #1776): N4 failed-clear test on a user's thread with raw first rows; BUILD.md notes the failed path has no verb route yet
4da907cd5 row 271 (264d, #1776): T4 tests round 1, not run (slot held). Bus session-clear.test.mjs (15), cli session-clear.test.mjs (3, bwrap) with clear-adapter.mjs; BUILD.md maps each T4 item and names what is still open (failed clear, launcher uncertain path, mutants)
aa8fc490f row 271 (264d, #1776): clear context build round 1, not run (slot held). Broker: session.clear, session.cancel and session.operation, the fence on send/open/claim, cancel at arrival for routed messages, the drained receive and the end drain, trusted ops sessionClears and sessionClearExited/Uncertain/Retire/Start. Launcher: clearPass before the threads loop (stop, proof, retire, successor start through check()/start()), waiting on a live row 269 survivor. Old tests per mos-1315, rebased on row 206 (sage-2968): label 3m to 3n, future step 3n to 3o, 3m-3n.sql in the engine copies and file lists, ready lines, later3l and the rows() exclusions gain both tables as 206 did for thread_shares (mos-1292, approved in mos-1336), schema.test counts 25 tables and 96 triggers, COUNTED and T45 LISTED gain both tables after thread_shares (mos-1318, mos-1319), TESTER_VERBS pin (mos-1325), fixtures/schema-3m.sql
a9884dc75 row 271 (264d, #1776): schema step 3m to 3n (rebased on row 206's 3l to 3m, sage-2968), session_ops and message_cancels with 7 triggers; successor id pinned as a lowercase version 8 UUID for Huey (gizmoduck-0274)
25c2c1a56 gizmoduck: row 271 design: duckworth-0171 C1 (start stages keyed on a report counter, work attempt in its own column) and C2 (cancelled in the stage CHECK at attempt 0 and closing the op, claim and cancels untouched by a pre-retire cancel, post-retire successor reading stated, race and held tests); N1 grep at build
4f827a5e8 gizmoduck: row 271 design: session.cancel and the cancelled stage built in 271 (sage-2828 re-rules Q3 per D7:263-264), pre- and post-retire paths, cancel tests and the fence-release mutant; N2 refuses session-name-too-long at accept
7dc8ff1bf gizmoduck: row 271 design: T45 LISTED gains session_ops and message_cancels to match COUNTED (mos-1319)
61df3061e gizmoduck: row 271 design: duckworth-0170 B1-B4 folded (cancel at arrival for routed messages, claim refused while clearing, op namespace both ways, attempt key on start stages) and N1-N8 answered; cancel recorded as a departure from D7:263-264
b212182ed gizmoduck: row 271 design: both new tables join COUNTED in stack-release.sh, so T46 passes unchanged; schema.test count pins name the 2 tables and 7 triggers; T45 LISTED question to Mos (mos-1318)
dac64ff31 gizmoduck: row 271 (264d) design draft 2: drained acknowledged by the run's own receive at a turn boundary (a landing turn has unread 0 with a child running), SIGTERM only after it; successor <old>-N bound at request with a deterministic id; no cancel (sage-2826); pre-existing test change list per mos-1315
b329ad69f gizmoduck: row 271 (264d) design draft 1 in the packet: the threadIdle fence as the drain, the idle stop as the only signal, a session_ops journal and message_cancels in one schema step; Q1-Q4 for Sage and Mos
8d4d4ec59 gizmoduck: row 270: the bus legacy test inserts its legacy thread by hand (sessions is append-only) and the test business has the assistant thread.create needs; not run
5ddc36ca9 gizmoduck: row 270 (264c, #1775): completed Pi compactions counted into a host ledger beside the session store, keyed (entry id, timestamp) store-wide (sage-2806, sage-2814); launcher collects at run start and end under the workspace lock; session.list rows gain compactions through a runtime-injected reader; not run (slot held)
f510d7367 gizmoduck: rows 270 and 271 packets: the 264c count and 264d clear contracts pinned for row 272 (Huey), and the Pi 0.85.1 compaction source contract
767bb62c7 docs: close remote credential and private review access findings
6345aae3d docs: align editor graph, Markdown transition and archive with PRD d6
ee5f7b9ef docs: align O2 chapter conversion and Case 2 access with owner rulings
0b923a275 MVP readiness briefs (mos-1371/1372): FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 grant scope #1804, 574 #1805, 573 #1806; docs only
b0a6838ca SESSIONS: Filbert Q195 O2 S0 review (#1798, c30914); doc only
062646487 Q195 O2 W4 (#1799): log lines for the W4 design and review landing, row 203 and O2 landing; docs only
66299eeef Q195 O2 W4 (#1799): Megavolt's round 4 review of 994c3f7fb (blob 2af460ab7): APPROVE, R3-B1 and R3-B2 closed, R4-N1 (re-migration after a pre-W4b restore) non-blocking for W4b; doc only
569bb6b17 Q195 O2 W4 (#1799): Megavolt's round 3 review of 54246cb32 (blob 0c4f55826): CHANGES, R2-B1 projection and R2-B2 closed, R3-B1 (reserved notes keyed on negative IDs) and R3-B2 (fence initial state and confirmation scope undefined); doc only
fb404c909 Q195 O2 W4 (#1799): Megavolt's round 2 review of 019a0ea5b (blob 14dfe92d9): CHANGES, B1-B9 and M1 closed, R2-B1 (paragraph-mark revisions, reserved-note leaves) and R2-B2 (release backup/restore keeps O2 text and revives revocations); doc only
4c0a2c4fd Q195 O2 W4 (#1799): Megavolt's review of the DOCX citation design at 15e91538f (blob 662abdb7c): CHANGES, B1-B9 and M1 (Case 2 narrowing in O2-context turns goes to Mos/Jason); doc only
2f06aad3f docs: define reserved notes and initial O2 restore fence
6863d4c85 docs: specify O2 projection marks and restore revocation fence
9ef5798ac docs: close W4 locator and access review gaps
4897520c3 docs: define committed DOCX citation and access contract
4ca270307 queue: revs 3360-3368. Row 203 (#1701): Sage brief re-pin to 8adfb261f (3360, Webby N2), Quackerjack claim, in-review r2 and outcome (3361-3363), Sage request as jarvis (3364-3365), Webby approve record (3366), Sage landing note and done (3367-3368).
25c09f19f docs: name save-group settings and enforce adoption gates
a6da8718c docs: fix O2 review gates, shipping and save semantics
ee26cfbad docs: redesign Q195 for full WordCraft O2 editor
a4da4e6df row 203: gate results on the rebased tip (c7b754d7b): stack-release 219/0, test-config 51/0, mutants 17/17, cli 895/0, ten scripts green with named skips; code tree unchanged from 7309260ae
7309260ae row 203: gate record on the rebased tip (c7b754d7b): blob table and timeout table, written before the first run
7c543fd4a row 203 FED R2: cases for missing self, peer id and label (webby-0191 B3)
284d55cf2 row 203 FED R2: stack-release probe reads federation; a refusal case per federation check
93a491d73 row 203 FED R2: federation key in the system config (#1701)
c7b754d7b queue: revs 3339-3359. Row 292 (#1797, Gander) added and briefed (Sage, 3339-3340). Row 291: Gosalyn claim and in-review r1 (3341, 3344-3345), Sage request as jarvis (3353-3354), Webby approve record (3357). Row 207: Megavolt in-review (3342), Sage abandon and request as jarvis, resolved on c30841 (3347, 3349-3350), Duckworth approve record (3356), Sage landing note and done (3358-3359). Row 270: Gizmoduck claim and in-review r3 (3343, 3346), Sage abandon and request as jarvis (3348, 3351-3352), Duckworth approve record (3355).
a94c95522 docs: row 207 landing records (SESSIONS, BUILD-LOG, Megavolt's lines), DEFERRED 207 N1 (duckworth-0230) and 270 N6 bound to row 272 (duckworth-0228)
c4f77373f row 207 (#1705): BUILD.md Runs records the gate on 712f79c0a (sage-3041): access.test 31/31, M1-M8 killed on their named tests, cli 894/894, ten scripts green under the mos-1275 limits, and the stopped first run's kill-check correction; doc only
c7e77202d row 207 (#1705): BUILD.md records sage-2810 (Q1 no reload, cited to broker.mjs:384; Q2 refuse with user-has-grants) and the usage/help pin sweep: access.test.mjs is the only reader, no hash pin; doc only
a0094633f row 207 (#1705): access.test.mjs T1-T9 for user add|remove; BUILD.md pins M7/M8 as edits and records the row 275 path check (bus only, morgana/row275 empty); not yet run, waits for Sage's CLEAR (sage-2790)
721268b08 row 207 (#1705): mosaic access user add|remove on R5's path (proof, lock, revision, checkAccess, receipt user.add/user.remove); remove refuses user-has-grants; --level viewer already in R5; BUILD.md records Q1-Q5 (no access.reload, R4's) and the tests and mutants; tests held for the row 275 manifest check (sage-2790)
f0e3dcdf4 plans: brief for the config_checks follow-up (row 270 rollback hazard, duckworth-0226, sage-3038)
01983cd3f queue: rev 3338. Row 224 waits on row 216 (Sage, mos-1351: della/row224 carries row 216's commit 4f3fbc25a).
626c19e78 queue: revs 3325-3337. Row 271: Gizmoduck claim and in-review r3 (3325-3327), Sage review request as jarvis (3328-3329), Duckworth approve record (3330). Row 276: Gander in-review r1 (3331-3332), Sage review request as jarvis (3333-3334), Duckworth approve record (3335), Sage landing note and done (3336-3337).
f32b867f3 docs(row276): record green run5 gate and all seventeen assertion kills
826e8fd7f docs(row276): refresh mos-1350 baseline cleanup packet and limits
e06130fc0 test(case): require baseline session cleanup per mos-1350 review
466562ba0 docs(row276): pin mos-1350 baseline candidate and timing audit
931fab079 perf(case): remove duplicate head validation per mos-1350
135bb8fae test(case): isolate P10 timing with per-mode baseline per mos-1350
cf228eabc docs(row276): record run4 stop and mos-1349 timing pairs
41f8bd197 docs(row276): packet stdout spool and M17 per mos-1345
47906e4fd fix(case): isolate legacy stdout after outer timeout per mos-1345
c2b845c76 docs(row276): preserve run3 whole-file stop for placement
74635fc60 fix(row276): correct multi-file driver path per mos-1337 review
257003ba1 test(row276): record mos-1337 N5 delta and both-copy mutant
537ce2dd4 fix(case): silence vanished stat opens per mos-1337 N5
3b9042802 docs(row276): record run2 stop for Mos placement
859ae3708 docs(row276): void setup-red run and pin first fixture correction
88179d6de test(row276): define read helper for new legacy fixtures
ccfa17eef docs(row276): integrate schema3m base and record dependency drift
b5d9efa1b docs(row276): pin integration on 94c33a43c and preserve upstream docs
69979bb8a docs(row276): record static approval and upstream deferred drift
155f64322 docs(row276): pin supervised-call candidate and fifteen mutants
b96b4e303 fix(case1): supervise legacy user calls across sudo sessions
f2735bb6a queue: revs 3317-3324. Row 206: Launchpad claim and in-review (3317-3319), Sage review request (3320-3321), Duckworth approve record (3322), Sage landing note and done (3323-3324).
bus: scripts/bus-service.sh install --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --pm --claude /home/jwoltje/.local/bin/claude
proof: off
send: off
Console: scripts/webui-service.sh install --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
CLI: scripts/stack-copy.sh wrapper 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --bin /home/jwoltje/.npm-global/bin
interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
# exit 0, end 2026-10-23T03:44:33.188Z
# W3-plan.txt sha256 229888d8d03a3be1a46ad505ed50f85c435aec580a161cbb2e7bceba03ae866b
# W3 commit list lines left out of the flag checks: 94
# W3 plan checks: OLD to NEW, 3m to 3n in one step, 94 commits, needs --migrate (given) and the decision 96 line, backup line, Console line OLD's flags exactly, proof off, send off, no drop/sso/send/proof, bus --pm: PASS
## W4 bus unit (Environment omitted from the transcript)
# start 2026-10-23T03:44:33.206Z
# command: (cd $REPO && <clean env> systemctl --user show [email protected] -p ExecStart -p WorkingDirectory -p DropInPaths)
DropInPaths=
ExecStart={ path=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/scripts/mosaic ; argv[]=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/scripts/mosaic bus start mosaic-stack --pm ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
# exit 0, end 2026-10-23T03:44:33.212Z
## W4 webui unit
# start 2026-10-23T03:44:33.214Z
# command: (cd $REPO && <clean env> systemctl --user show mosaic-webui.service -p ExecStart -p WorkingDirectory -p DropInPaths)
DropInPaths=
ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/packages/webui/src/cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
# exit 0, end 2026-10-23T03:44:33.219Z
# W4 checks: DropInPaths empty on both, both ExecStart name the OLD copy, bus has --pm, no quote or backslash in either ExecStart
# W4 bus Environment: 2 assignments; quote or backslash: none (values not recorded)
# W0b-W4 end 2026-10-23T03:44:33Z
# W5 pre-read start 2026-10-23T03:45:42Z
## W5 pre agents --tester
# start 2026-10-23T03:45:42.086Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic agents --business mosaic-stack --tester)
chief r148e72c8895d claude-code since 2026-10-22T20:16:24.301Z
# exit 0, end 2026-10-23T03:45:42.304Z
## W5 pre launches list
# start 2026-10-23T03:45:42.307Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic launches list)
r148e72c8895d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-22T20:16:24.291Z running
# exit 0, end 2026-10-23T03:45:42.424Z
# W5 pre-read PASS 2026-10-23T03:45:42Z: chief r148e72c8895d standing, not working; no pm, no other launch
# W5 pre-read start 2026-10-23T03:45:57Z
## W5 pre agents --tester
# start 2026-10-23T03:45:57.395Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic agents --business mosaic-stack --tester)
chief r148e72c8895d claude-code since 2026-10-22T20:16:24.301Z
# exit 0, end 2026-10-23T03:45:57.606Z
## W5 pre launches list
# start 2026-10-23T03:45:57.610Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic launches list)
r148e72c8895d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-22T20:16:24.291Z running
# exit 0, end 2026-10-23T03:45:57.720Z
# W5 pre-read PASS 2026-10-23T03:45:57Z: chief r148e72c8895d standing, not working; no pm, no other launch
# W5 go mos-1404, start 2026-10-23T03:45:57Z
# condition 3 recheck: tool blob 7a8c347b2be4, plan e664fa35, four scripts clean, pointer on OLD, P2 stubs and units PASS
# W5 exit 0, W5.start 2026-10-23T03:45:57Z, end 2026-10-23T03:46:02Z
# W5: activation 20261023T034557Z-464010fbe4c6, backup /home/jwoltje/.mosaic-dev/backups/stack/20261023T034557Z-464010fbe4c6, STAMP 2026-10-23T03:45:58.274Z, SCHEMA 3m, SHA256SUMS sha256 480f5e556d02d3f079c07782af204c0a41e44fb4633064e1d7c302e473be1390 (sums check OK)
# W5 launched lines in the bus journal since W5.start: 1: launched chief: run r28dbbc5c6b4d, claude-code claude-opus-5-5
## W5 status
# start 2026-10-23T03:46:02.315Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh status mosaic-stack)
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
# exit 0, end 2026-10-23T03:46:02.590Z
# W5 status: pointer NEW, pm true, 3n, check pass, unit flags match. W5_PASS 2026-10-23T03:46:02Z
## W5 rollback --restore 20261023T034557Z-464010fbe4c6 --plan (read only)
# start 2026-10-23T03:46:02.596Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh rollback mosaic-stack --restore 20261023T034557Z-464010fbe4c6 --plan)
plan: rollback for mosaic-stack
from: 464010fbe4c6925b077b9b8182a209ee2177eb9d
to: 0cbe2e9b35e51fe06dedde69c022b46e040f98c0
store schema: 3n (read from the store the running bus holds)
target schema: 3m
restore: the store must go back to 3m; needs --restore 20261023T034557Z-464010fbe4c6
commits: the run takes away 94 (newest first, at most 200):
464010fbe queue: revs 3435-3436. Row 294 (#1800): Duckworth APPROVE c30998 (3435, candidate 0b3f9c9d). Row 272 (#1777): Scrooge CHANGES c31006 (3436, candidate 0c3dc2ed); queue only
e57bfde73 queue: revs 3407-3434. Row 293 (574, #1805): Gander in-review r1 (3407, failed pre-send 3408), Sage request c30981 (3409/3410), Filbert APPROVE c30982 (3411). Row 292 (#1797): assigned gizmoduck (mos-1387), claim 3413, in-review r1 3419 (3420 failed), Sage request c30992 (3425/3426), Duckworth APPROVE c30975 (3433). Row 294 (#1800): Launchpad claim 3418, in-review r1 3422 (3423 failed), Sage request c30993 (3427/3428). Row 295 (#1801): Morgana claim 3414, in-review r1 3416 (3417 failed), Sage request c30991 resolved (3421/3424), Dewey CHANGES c30994 (3431). Row 272 (#1777): Huey claim 3415, in-review r1 3429 (3430 failed), Sage request c30997 resolved (3432/3434); queue only
319d9f87e sessions: Filbert row 293 (Vikunja 574) APPROVE c30982 rev 3411, plus re-added S0 re-check and 574 static review lines; docs only
b23783f39 Rows 270+271 landing (7328da8ca), 571 landing, MVP rows 293-299: BUILD-LOG phase line and Sage SESSIONS line; docs only
e18ad2711 docs: define mixed-format chapter ordering for conversion
12f8c3089 queue: rows 293-299 briefed (revs 3399, 3401-3406, sage) and row 293 claimed by gander (rev 3400, gander-293-start); queue only
253346e3c queue: rows 293-299 added (574 #1805, FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 #1804, 573 #1806; mos-1371/1372), 208 after 294; 270 round 3 request c30929 and Duckworth APPROVE c30930; 270+271 done at 7328da8ca (mos-1351); queue only
7328da8ca gizmoduck: row 270 (264c, #1775) gate results: run 3 red on case1-accept P10 band (Vikunja 573) and run 4 red on the checkout.test fixture race (Vikunja 574), both old tests unchanged in the range; run 5 GREEN on 53406f3e4 under mos-1369 and sage-3106, all phases and 38/38 mutants killed; BUILD.md only
84002f960 gizmoduck: row 270 (264c, #1775) gate run 2 second red, fixed under mos-1364 and sage-3093: compactions-budget.test F1 (:39 input 8.5 * MiB is a whole number, now MiB + 0.5) and F2 (the 0o000 window in try/finally; the after-hook ran after setup's rm); K M22 :129, M23 :120, M22 and M15 notes; BUILD.md run 2
f89aaf57f gizmoduck: row 270 (264c, #1775) gate run 1 red placed (mos-1272): compactions-launch.test's host wires row 271's five clear ops, as session-clear.test does; the clear test's successor never started without them; kill lines +5 in mutants.py K; BUILD.md run 1
d3a49f301 gizmoduck: rows 270+271 combined gate (C2) record before the first run (sage-3073, sage-3086, mos-1275, mos-1351): blob table, timeout table, GREEN and RED, order; code is ac44d0de9's
562b05b43 gizmoduck: row 270 (264c, #1775) round 4 for sage-3073 and sage-3078: rows 270 and 271 rebased onto 25c09f19f (row 203 plus the O2 docs); mosaic-config.mjs keeps 203's federation key and both compaction keys; BUILD.md round 4 with the range-diff, the three merged files and C1 after 203; not run
8c28ee731 gizmoduck: row 270 (264c, #1775) round 3 for duckworth-0226 B4 (sage-3038): one collection opens at most the system config's compactionStreams stream files in name order (default 1024, 32 to 65536, a bad value takes the default and logs), not-collected past it with no cursor for an unopened stream; the missing-stream check uses a Set; readStreams, cap and launcher tests; mutants M22-M24, M10 anchor moved; docs/TOOLS.md has both keys and C1 (N4); BUILD.md round 3 with stream counts, the 231 KB bound, C1 and N5; not run
bce491715 gizmoduck: row 270 (264c, #1775) round 2 for duckworth-0223 B1-B3 (sage-3030, sage-3031, mos-1352, sage-3033): pi streams read in 1 MiB chunks under a budget from the system config's compactionReadBytes (default 64 MiB, 1 MiB to 1 GiB, a bad value takes the default and logs), not-collected past it; Claude history from before the ledger is harness-unsupported for good; launcher, runtime reader, clear and legacy tests; mutants M13-M21; BUILD.md round 2 with sizes, schema change, rollback hazard, N1-N3; not run
01b57f48b gizmoduck: row 270 (264c, #1775) round 1 for review (sage-3024): mutants.py M1-M12 with anchors at 7d199f033, each matching once, kills read from the tests; run plan with broker.mjs reruns; launcher and runtime wiring named as untested; not run
54ec0005e row 271 (264d, #1776): BUILD.md r3 gate on 9e18a9aaf (sage-3017): cli and bus session-clear.test whole green (5/5, 19/19), cli suite 874/874, M14 killed by the T6 test's first lines() assertion at :365; no first-run red. Doc only.
61e262b7c row 271 (264d, #1776): r3 packet carries mutants.py, M1-M14 with their anchors, as the gate applies them (duckworth-0212 C1). Doc and harness only; no src or test change.
540b6f16b row 271 (264d, #1776): r3, T6 per sage-3001. New cli test: a bus unavailable (broker-exited) at the exit proof, then at retirement, holds the clear with no retirement, successor launch or slot handed on, logs once, and resumes when the bus answers. BUILD.md maps the halt case to session-launch.test.mjs:372 and launcher.mjs:824, binds the Q198 hold to row 257 with duckworth-0210's carry, and names M14 (drop clearPass's try/catch) and its kill. No src change. Not run (no CLEAR).
e6a675ee8 row 271 (264d, #1776): BUILD.md gate results on e8af389e6 (sage-2991): 18 touched files, bus/seat/cli suites and every test-*.sh green, M1-M13 killed by assertions in the named tests; the row gate's T6 coverage open to Sage. Doc only.
e77635ceb row 271 (264d, #1776): BUILD.md run limits for the e567f949c gate (mos-1275, sage-2991); mutant lines moved one down past the B1 comment. Doc only.
3fc8ccef1 row 271 (264d, #1776): duckworth-0201 B1: #ownerRefusal passes 'session.clear' to #granted, so the project and instance sit in their own slots and a live collaborator grant passes the retirement recheck. N4 setup shared; a second N4 test keeps the grant and expects retired plus the successor; M13 restores the defect. 264e carry noted at the N4 tests. Not run (no CLEAR).
27d3266ff row 271 (264d, #1776): BUILD.md records mos-1336, Mos's approval under mos-1292 of the later3l, rows() and thread-shares migration test changes on the 3m to 3n rebase, with its conditions
6bd67225b row 271 (264d, #1776): BUILD.md states the post-rebase '3m' grep result as measured
14715d644 row 271 (264d, #1776): r2 on 0cbe2e9b3 (sage-2968): 3m to 3n migration and 3m engine tests from fixtures/schema-3m.sql; BUILD.md records the label change, the old-test changes (later3l, rows() and the thread-shares migration test outside the mos-1315 list, for Mos) and the mutant lines on the rebased tree
f9a9c9b5a row 271 (264d, #1776): BUILD.md defines M1-M12 with their expected kills
34388223d row 271 (264d, #1776): launcher uncertain-path test with test-driven ticks and a stopped runner; start() takes stopTimeoutMs
3adb391b5 row 271 (264d, #1776): N4 failed-clear test on a user's thread with raw first rows; BUILD.md notes the failed path has no verb route yet
4da907cd5 row 271 (264d, #1776): T4 tests round 1, not run (slot held). Bus session-clear.test.mjs (15), cli session-clear.test.mjs (3, bwrap) with clear-adapter.mjs; BUILD.md maps each T4 item and names what is still open (failed clear, launcher uncertain path, mutants)
aa8fc490f row 271 (264d, #1776): clear context build round 1, not run (slot held). Broker: session.clear, session.cancel and session.operation, the fence on send/open/claim, cancel at arrival for routed messages, the drained receive and the end drain, trusted ops sessionClears and sessionClearExited/Uncertain/Retire/Start. Launcher: clearPass before the threads loop (stop, proof, retire, successor start through check()/start()), waiting on a live row 269 survivor. Old tests per mos-1315, rebased on row 206 (sage-2968): label 3m to 3n, future step 3n to 3o, 3m-3n.sql in the engine copies and file lists, ready lines, later3l and the rows() exclusions gain both tables as 206 did for thread_shares (mos-1292, approved in mos-1336), schema.test counts 25 tables and 96 triggers, COUNTED and T45 LISTED gain both tables after thread_shares (mos-1318, mos-1319), TESTER_VERBS pin (mos-1325), fixtures/schema-3m.sql
a9884dc75 row 271 (264d, #1776): schema step 3m to 3n (rebased on row 206's 3l to 3m, sage-2968), session_ops and message_cancels with 7 triggers; successor id pinned as a lowercase version 8 UUID for Huey (gizmoduck-0274)
25c2c1a56 gizmoduck: row 271 design: duckworth-0171 C1 (start stages keyed on a report counter, work attempt in its own column) and C2 (cancelled in the stage CHECK at attempt 0 and closing the op, claim and cancels untouched by a pre-retire cancel, post-retire successor reading stated, race and held tests); N1 grep at build
4f827a5e8 gizmoduck: row 271 design: session.cancel and the cancelled stage built in 271 (sage-2828 re-rules Q3 per D7:263-264), pre- and post-retire paths, cancel tests and the fence-release mutant; N2 refuses session-name-too-long at accept
7dc8ff1bf gizmoduck: row 271 design: T45 LISTED gains session_ops and message_cancels to match COUNTED (mos-1319)
61df3061e gizmoduck: row 271 design: duckworth-0170 B1-B4 folded (cancel at arrival for routed messages, claim refused while clearing, op namespace both ways, attempt key on start stages) and N1-N8 answered; cancel recorded as a departure from D7:263-264
b212182ed gizmoduck: row 271 design: both new tables join COUNTED in stack-release.sh, so T46 passes unchanged; schema.test count pins name the 2 tables and 7 triggers; T45 LISTED question to Mos (mos-1318)
dac64ff31 gizmoduck: row 271 (264d) design draft 2: drained acknowledged by the run's own receive at a turn boundary (a landing turn has unread 0 with a child running), SIGTERM only after it; successor <old>-N bound at request with a deterministic id; no cancel (sage-2826); pre-existing test change list per mos-1315
b329ad69f gizmoduck: row 271 (264d) design draft 1 in the packet: the threadIdle fence as the drain, the idle stop as the only signal, a session_ops journal and message_cancels in one schema step; Q1-Q4 for Sage and Mos
8d4d4ec59 gizmoduck: row 270: the bus legacy test inserts its legacy thread by hand (sessions is append-only) and the test business has the assistant thread.create needs; not run
5ddc36ca9 gizmoduck: row 270 (264c, #1775): completed Pi compactions counted into a host ledger beside the session store, keyed (entry id, timestamp) store-wide (sage-2806, sage-2814); launcher collects at run start and end under the workspace lock; session.list rows gain compactions through a runtime-injected reader; not run (slot held)
f510d7367 gizmoduck: rows 270 and 271 packets: the 264c count and 264d clear contracts pinned for row 272 (Huey), and the Pi 0.85.1 compaction source contract
767bb62c7 docs: close remote credential and private review access findings
6345aae3d docs: align editor graph, Markdown transition and archive with PRD d6
ee5f7b9ef docs: align O2 chapter conversion and Case 2 access with owner rulings
0b923a275 MVP readiness briefs (mos-1371/1372): FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 grant scope #1804, 574 #1805, 573 #1806; docs only
b0a6838ca SESSIONS: Filbert Q195 O2 S0 review (#1798, c30914); doc only
062646487 Q195 O2 W4 (#1799): log lines for the W4 design and review landing, row 203 and O2 landing; docs only
66299eeef Q195 O2 W4 (#1799): Megavolt's round 4 review of 994c3f7fb (blob 2af460ab7): APPROVE, R3-B1 and R3-B2 closed, R4-N1 (re-migration after a pre-W4b restore) non-blocking for W4b; doc only
569bb6b17 Q195 O2 W4 (#1799): Megavolt's round 3 review of 54246cb32 (blob 0c4f55826): CHANGES, R2-B1 projection and R2-B2 closed, R3-B1 (reserved notes keyed on negative IDs) and R3-B2 (fence initial state and confirmation scope undefined); doc only
fb404c909 Q195 O2 W4 (#1799): Megavolt's round 2 review of 019a0ea5b (blob 14dfe92d9): CHANGES, B1-B9 and M1 closed, R2-B1 (paragraph-mark revisions, reserved-note leaves) and R2-B2 (release backup/restore keeps O2 text and revives revocations); doc only
4c0a2c4fd Q195 O2 W4 (#1799): Megavolt's review of the DOCX citation design at 15e91538f (blob 662abdb7c): CHANGES, B1-B9 and M1 (Case 2 narrowing in O2-context turns goes to Mos/Jason); doc only
2f06aad3f docs: define reserved notes and initial O2 restore fence
6863d4c85 docs: specify O2 projection marks and restore revocation fence
9ef5798ac docs: close W4 locator and access review gaps
4897520c3 docs: define committed DOCX citation and access contract
4ca270307 queue: revs 3360-3368. Row 203 (#1701): Sage brief re-pin to 8adfb261f (3360, Webby N2), Quackerjack claim, in-review r2 and outcome (3361-3363), Sage request as jarvis (3364-3365), Webby approve record (3366), Sage landing note and done (3367-3368).
25c09f19f docs: name save-group settings and enforce adoption gates
a6da8718c docs: fix O2 review gates, shipping and save semantics
ee26cfbad docs: redesign Q195 for full WordCraft O2 editor
a4da4e6df row 203: gate results on the rebased tip (c7b754d7b): stack-release 219/0, test-config 51/0, mutants 17/17, cli 895/0, ten scripts green with named skips; code tree unchanged from 7309260ae
7309260ae row 203: gate record on the rebased tip (c7b754d7b): blob table and timeout table, written before the first run
7c543fd4a row 203 FED R2: cases for missing self, peer id and label (webby-0191 B3)
284d55cf2 row 203 FED R2: stack-release probe reads federation; a refusal case per federation check
93a491d73 row 203 FED R2: federation key in the system config (#1701)
c7b754d7b queue: revs 3339-3359. Row 292 (#1797, Gander) added and briefed (Sage, 3339-3340). Row 291: Gosalyn claim and in-review r1 (3341, 3344-3345), Sage request as jarvis (3353-3354), Webby approve record (3357). Row 207: Megavolt in-review (3342), Sage abandon and request as jarvis, resolved on c30841 (3347, 3349-3350), Duckworth approve record (3356), Sage landing note and done (3358-3359). Row 270: Gizmoduck claim and in-review r3 (3343, 3346), Sage abandon and request as jarvis (3348, 3351-3352), Duckworth approve record (3355).
a94c95522 docs: row 207 landing records (SESSIONS, BUILD-LOG, Megavolt's lines), DEFERRED 207 N1 (duckworth-0230) and 270 N6 bound to row 272 (duckworth-0228)
c4f77373f row 207 (#1705): BUILD.md Runs records the gate on 712f79c0a (sage-3041): access.test 31/31, M1-M8 killed on their named tests, cli 894/894, ten scripts green under the mos-1275 limits, and the stopped first run's kill-check correction; doc only
c7e77202d row 207 (#1705): BUILD.md records sage-2810 (Q1 no reload, cited to broker.mjs:384; Q2 refuse with user-has-grants) and the usage/help pin sweep: access.test.mjs is the only reader, no hash pin; doc only
a0094633f row 207 (#1705): access.test.mjs T1-T9 for user add|remove; BUILD.md pins M7/M8 as edits and records the row 275 path check (bus only, morgana/row275 empty); not yet run, waits for Sage's CLEAR (sage-2790)
721268b08 row 207 (#1705): mosaic access user add|remove on R5's path (proof, lock, revision, checkAccess, receipt user.add/user.remove); remove refuses user-has-grants; --level viewer already in R5; BUILD.md records Q1-Q5 (no access.reload, R4's) and the tests and mutants; tests held for the row 275 manifest check (sage-2790)
f0e3dcdf4 plans: brief for the config_checks follow-up (row 270 rollback hazard, duckworth-0226, sage-3038)
01983cd3f queue: rev 3338. Row 224 waits on row 216 (Sage, mos-1351: della/row224 carries row 216's commit 4f3fbc25a).
626c19e78 queue: revs 3325-3337. Row 271: Gizmoduck claim and in-review r3 (3325-3327), Sage review request as jarvis (3328-3329), Duckworth approve record (3330). Row 276: Gander in-review r1 (3331-3332), Sage review request as jarvis (3333-3334), Duckworth approve record (3335), Sage landing note and done (3336-3337).
f32b867f3 docs(row276): record green run5 gate and all seventeen assertion kills
826e8fd7f docs(row276): refresh mos-1350 baseline cleanup packet and limits
e06130fc0 test(case): require baseline session cleanup per mos-1350 review
466562ba0 docs(row276): pin mos-1350 baseline candidate and timing audit
931fab079 perf(case): remove duplicate head validation per mos-1350
135bb8fae test(case): isolate P10 timing with per-mode baseline per mos-1350
cf228eabc docs(row276): record run4 stop and mos-1349 timing pairs
41f8bd197 docs(row276): packet stdout spool and M17 per mos-1345
47906e4fd fix(case): isolate legacy stdout after outer timeout per mos-1345
c2b845c76 docs(row276): preserve run3 whole-file stop for placement
74635fc60 fix(row276): correct multi-file driver path per mos-1337 review
257003ba1 test(row276): record mos-1337 N5 delta and both-copy mutant
537ce2dd4 fix(case): silence vanished stat opens per mos-1337 N5
3b9042802 docs(row276): record run2 stop for Mos placement
859ae3708 docs(row276): void setup-red run and pin first fixture correction
88179d6de test(row276): define read helper for new legacy fixtures
ccfa17eef docs(row276): integrate schema3m base and record dependency drift
b5d9efa1b docs(row276): pin integration on 94c33a43c and preserve upstream docs
69979bb8a docs(row276): record static approval and upstream deferred drift
155f64322 docs(row276): pin supervised-call candidate and fifteen mutants
b96b4e303 fix(case1): supervise legacy user calls across sudo sessions
f2735bb6a queue: revs 3317-3324. Row 206: Launchpad claim and in-review (3317-3319), Sage review request (3320-3321), Duckworth approve record (3322), Sage landing note and done (3323-3324).
bus: scripts/bus-service.sh install --copy 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 --copies /home/jwoltje/.local/share/mosaic-stack/copies --pm --claude /home/jwoltje/.local/bin/claude
proof: off
send: off
Console: scripts/webui-service.sh install --copy 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 --copies /home/jwoltje/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
CLI: scripts/stack-copy.sh wrapper 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 --copies /home/jwoltje/.local/share/mosaic-stack/copies --bin /home/jwoltje/.npm-global/bin
interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
# exit 0, end 2026-10-23T03:46:02.897Z
# W5 restore plan exit 0: restore: the store must go back to 3m; needs --restore 20261023T034557Z-464010fbe4c6
# W6 start 2026-10-23T03:46:51Z
## W6 status
# start 2026-10-23T03:46:51.686Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh status mosaic-stack)
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
# exit 0, end 2026-10-23T03:46:51.968Z
# W6 status: "copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d" "pm":true "schema":"3n"
## W6 live store count (python3 -B, mode=ro)
types table=26 trigger=97 view=5 index=28
schema_version 3n
missing none
new_table_rows session_ops=0 message_cancels=0 thread_shares 0
events 2202 maxSeq 2202
COUNTS_OK
# exit 0
## W6 agents --tester
# start 2026-10-23T03:46:52.031Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic agents --business mosaic-stack --tester)
chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z
# exit 0, end 2026-10-23T03:46:52.240Z
# W6 agents: chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z;
## W6 tester thread reads (tr.sh W6)
W6 threads --tester: 5 thread(s), threads.json sha256 07c154d75d929028
W6 thread d0ba7a66-aa3f-4918-9518-94132bce6b28 messages 2 sha256 985ac61ca9220f2f
W6 thread 446201de-e8b5-4df3-9356-d0d5422cc899 messages 2 sha256 7e7e9eeeaa8c1e09
W6 thread b89fcca4-6b82-48bd-9310-fb82c5ae8518 messages 2 sha256 f4e4916b9656a403
W6 thread d9bb7ff3-7a8b-4bb8-ba12-03ca094cee4e messages 2 sha256 728ff36f3e003c63
W6 thread b85a57fe-84f9-4785-a596-135af39e334a messages 2 sha256 727de0d9c809a41a
# exit 0
# W6 thread reads match W1: 5 threads, same ids, message counts and sha256
# W6 bus-host/sessions.json: sessionsVersion and session count: 2 1; mtime 2026-10-23T03:46:01Z
# W6_PASS 2026-10-23T03:46:56Z
# W9 after W5, W5.start 2026-10-23T03:45:57Z, start 2026-10-23T03:47:16Z
# plan e664fa35, tool blob 7a8c347b2be4, console-check.sh d068fe02, wrapper on NEW: checked
## W9 Console unit (mos-1338 item 4)
is-active active, MainPID 3811007
ExecMainStartTimestamp Thu 2026-10-22 22:45:59 CDT (2026-10-23T03:45:59Z), ps lstart Thu Oct 22 22:45:58 2026
cwd -> /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
# Console: unit active, pid 3811007, started 2026-10-23T03:45:59Z, cwd /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
# bus: unit active, pid 3811005, cwd /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
## W9 status
# start 2026-10-23T03:47:16.472Z
# command: (cd $REPO && <clean env> /mnt/storage/src/mosaic-stack/scripts/stack-release.sh status mosaic-stack)
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
# exit 0, end 2026-10-23T03:47:16.746Z
## W9 console-check NEW (/mnt/storage/src/mosaic-stack/agents/sage/work/window-plan/repin-de352dfaf/console-check.sh, d068fe02)
# start 2026-10-23T03:47:16.752Z
# command: (cd $REPO && <clean env> bash /mnt/storage/src/mosaic-stack/agents/sage/work/window-plan/repin-de352dfaf/console-check.sh 464010fbe4c6925b077b9b8182a209ee2177eb9d)
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=ae0b17c9586e51da7bc1b7e548c6acf913a55665 files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=ae0b17c9586e51da7bc1b7e548c6acf913a55665 length=99 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
# exit 0, end 2026-10-23T03:47:17.019Z
## W9 webui journal since 2026-10-23 03:45:57 UTC: 'Proxy: proxy proof off' and 'Sign-in:' counts
proxy proof off 1, Sign-in 0
# journal since W5: proxy proof off 1, Sign-in 0
## W9 tester socket
console/ drwx------ jwoltje directory
tester.sock srw------- jwoltje socket
# console/ drwx------ jwoltje directory; tester.sock srw------- jwoltje socket
## W9 peercred-starts.jsonl lines since W5 naming NEW
1 (file lines 5)
# peercred-starts lines since W5 naming NEW: 1
## W9 bus journal 'library:' lines since W5
library: 0
# bus journal library: 0
## W9 launches list
# start 2026-10-23T03:47:17.086Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic launches list)
r28dbbc5c6b4d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-23T03:46:01.221Z running
# exit 0, end 2026-10-23T03:47:17.196Z
## W9 launches list --json
# start 2026-10-23T03:47:17.198Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic launches list --json)
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r28dbbc5c6b4d",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 3811352,
"startTime": "82835140",
"runnerPid": 3811353,
"runnerStartTime": "82835140",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r28dbbc5c6b4d",
"startedAt": "2026-10-23T03:46:01.221Z",
"live": true
}
]
# exit 0, end 2026-10-23T03:47:17.310Z
# one running chief from this start: run r28dbbc5c6b4d
# pm: no pm launch is listed, as expected (none live since the last window; mos-1344)
## W9 agents --tester
# start 2026-10-23T03:47:17.340Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic agents --business mosaic-stack --tester)
chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z
# exit 0, end 2026-10-23T03:47:17.551Z
# W9 end 2026-10-23T03:47:17Z W9_CHECKS_PASS
# W9b start 2026-10-23T03:47:25Z
# before the relay: chief r28dbbc5c6b4d live, no pm
## W9b backup first (b0.sh 3n)
B0 (3n) start 2026-10-23T03:47:25.189Z
/home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z:
total 24
drwx------ 3 jwoltje jwoltje 4096 Oct 22 22:47 .
drwx------ 16 jwoltje jwoltje 4096 Oct 22 22:47 ..
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:47 bus
-rw------- 1 jwoltje jwoltje 37 Oct 22 22:47 CHECK
-rw------- 1 jwoltje jwoltje 3 Oct 22 22:47 SCHEMA
-rw------- 1 jwoltje jwoltje 77 Oct 22 22:47 SHA256SUMS
/home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z/bus:
total 3032
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:47 .
drwx------ 3 jwoltje jwoltje 4096 Oct 22 22:47 ..
-rw------- 1 jwoltje jwoltje 3096576 Oct 22 22:47 bus.sqlite
SHA256SUMS: 69702ac9a74ec7d52a7377565c1aa2a2db605fa1f5b9e9f6b38631df3acffebf bus.sqlite
SCHEMA: 3n
CHECK: ok events 2202 maxSeq 2202 schema 3n
B0 path /home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z
B0 SHA256SUMS sha256 0946b66ccde18f5be8d49ed9f15cccf09f73804abdf31b3ae9c108c6c9ee9272
B0 end 2026-10-23T03:47:25.308Z B0_PASS
# exit 0
# backup /home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z, SHA256SUMS 0946b66ccde18f5be8d49ed9f15cccf09f73804abdf31b3ae9c108c6c9ee9272
## W9b talk chief --wait 300 --tester (relay to pm)
# start 2026-10-23T03:47:25.313Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic talk chief [mos test] Please relay this to the pm: [mos test] read packages/tasks/requirements.json through your requirements read and reply with the PRD version and the number of ids. --wait 300 --business mosaic-stack --tester)
sent c66b0184 to chief
chief (RESULT, re c66b0184) 11e140fa:
I passed your request to the pm in your own words (message 3479e027-0099-4ce8-a106-0b1115e00dd2), with your note that it's a test and authorizes nothing. You asked for the PRD version and the number of ids in packages/tasks/requirements.json, read through its requirements read.
No pm session was running when I sent it, so I launched one (run ra38826f3c6e2, claude-code, claude-opus-5-5). Nothing was refused. When the pm answers, I'll pass on what it says.
# exit 0, end 2026-10-23T03:47:40.496Z
## W9b launches list
# start 2026-10-23T03:47:40.615Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic launches list)
r28dbbc5c6b4d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-23T03:46:01.221Z running
ra38826f3c6e2 mosaic-stack/pm claude-code claude-opus-5-5 by chief since 2026-10-23T03:47:35.835Z running
# exit 0, end 2026-10-23T03:47:40.731Z
# pm run ra38826f3c6e2 (claude-code), launchedBy chief, startedAt 2026-10-23T03:47:35.835Z, pid 3831373, runner pid 3831374: left live by W9b
# pm launched by chief after the relay: run ra38826f3c6e2 (claude-code), pid 3831373, runner pid 3831374
# pid 3831373: argv entries naming NEW's copy 37, cwd names NEW's copy no
# pid 3831374: argv entries naming NEW's copy 1, cwd names NEW's copy no
# the pm runs from NEW's copy
## W9b pm read talk pm --wait 300 --tester
# start 2026-10-23T03:47:40.822Z
# command: (cd $REPO && <clean env> /home/jwoltje/.npm-global/bin/mosaic talk pm [mos test] read packages/tasks/requirements.json through your requirements read and reply with one line: the version and the number of ids --wait 300 --business mosaic-stack --tester)
sent a2d9eeb9 to pm
pm (RESULT, re a2d9eeb9) 4500c1dd:
Version 1.15, 85 ids (checked 10 seconds ago with list_requirements and against packages/tasks/requirements.json at 464010f).
# exit 0, end 2026-10-23T03:47:51.812Z
# pm read: 1.15 and 85 found in the reply
# W9b end 2026-10-23T03:47:51Z W9B_PASS
# W10 start 2026-10-23T03:48:46Z, TMPDIR /mnt/storage/scratch/tmp/gKMdQdA
## W10 matrix-new.txt vs matrix-old.txt (header lines differ by stamp)
(diff exit 0)
# matrix: a-g unchanged expected: FAIL=0
## W10 listeners on :7333
count 0
# listeners on 7333: 0
## W10 tester reads through /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d/packages/cli/src/tester-http.mjs
/api/config 200 73 bytes sha256 e7ef9a40a725f356 keys ["board","tester","send","edit"] body {"board":"http://127.0.0.1:7331","tester":true,"send":false,"edit":false} (exit 0)
/api/bus/agents 200 709 bytes sha256 bc90ef3c3ae1a464 keys ["rows","at"] agents 2 (exit 0)
# tester /api/bus/agents 200, agents 2 (P6: agents 1; the hash moves with the at field)
# tester /api/config line identical to tester-old.txt
## W10 P4 again
# P4 2026-10-23T03:48:46Z
DropInPaths=
ExecStart={ path=/home/jwoltje/.local/bin/chisel ; argv[]=/home/jwoltje/.local/bin/chisel client --fingerprint ce+ISFvMAhuoCRDuc7riIf+hYz87TLHk/C9C/H8QsUg= --keepalive 25s --max-retry-interval 10s http://10.1.1.227:8080 R:0.0.0.0:7332:127.0.0.1:7332 ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
# exit 0
# R: specs (1): R:0.0.0.0:7332:127.0.0.1:7332
P4 PASS: one R: spec R:0.0.0.0:7332:127.0.0.1:7332, DropInPaths empty
(p4 exit 0)
## W10 unsigned writes through the main port (no session, no proxy header; must be refused)
POST /api/seen 401 error="sign-in required"
POST /api/reply 401 error="sign-in required"
POST /api/processing 401 error="sign-in required"
tester POST /api/processing 403 error="read-only" sent=false (exit 2)
# W10 end 2026-10-23T03:48:47Z FAIL=0
@@ -0,0 +1,17 @@
plan: activate for mosaic-stack
from: 0cbe2e9b35e51fe06dedde69c022b46e040f98c0
to: 464010fbe4c6925b077b9b8182a209ee2177eb9d
store schema: 3m (read from the store the running bus holds)
target schema: 3n
migration: 3m to 3n, after a backup of the stopped store
migration steps: 3m to 3n
needs: --migrate (given)
needs: Jason present for this run (decision 96 condition 2)
backup: the run backs up the stopped store to /home/jwoltje/.mosaic-dev/backups/stack/<activation>/ before any migration
commits: the run adds 94 (newest first, at most 200):
bus: scripts/bus-service.sh install --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --pm --claude /home/jwoltje/.local/bin/claude
proof: off
send: off
Console: scripts/webui-service.sh install --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
CLI: scripts/stack-copy.sh wrapper 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --bin /home/jwoltje/.npm-global/bin
interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
@@ -0,0 +1,111 @@
plan: activate for mosaic-stack
from: 0cbe2e9b35e51fe06dedde69c022b46e040f98c0
to: 464010fbe4c6925b077b9b8182a209ee2177eb9d
store schema: 3m (read from the store the running bus holds)
target schema: 3n
migration: 3m to 3n, after a backup of the stopped store
migration steps: 3m to 3n
needs: --migrate (given)
needs: Jason present for this run (decision 96 condition 2)
backup: the run backs up the stopped store to /home/jwoltje/.mosaic-dev/backups/stack/<activation>/ before any migration
commits: the run adds 94 (newest first, at most 200):
464010fbe queue: revs 3435-3436. Row 294 (#1800): Duckworth APPROVE c30998 (3435, candidate 0b3f9c9d). Row 272 (#1777): Scrooge CHANGES c31006 (3436, candidate 0c3dc2ed); queue only
e57bfde73 queue: revs 3407-3434. Row 293 (574, #1805): Gander in-review r1 (3407, failed pre-send 3408), Sage request c30981 (3409/3410), Filbert APPROVE c30982 (3411). Row 292 (#1797): assigned gizmoduck (mos-1387), claim 3413, in-review r1 3419 (3420 failed), Sage request c30992 (3425/3426), Duckworth APPROVE c30975 (3433). Row 294 (#1800): Launchpad claim 3418, in-review r1 3422 (3423 failed), Sage request c30993 (3427/3428). Row 295 (#1801): Morgana claim 3414, in-review r1 3416 (3417 failed), Sage request c30991 resolved (3421/3424), Dewey CHANGES c30994 (3431). Row 272 (#1777): Huey claim 3415, in-review r1 3429 (3430 failed), Sage request c30997 resolved (3432/3434); queue only
319d9f87e sessions: Filbert row 293 (Vikunja 574) APPROVE c30982 rev 3411, plus re-added S0 re-check and 574 static review lines; docs only
b23783f39 Rows 270+271 landing (7328da8ca), 571 landing, MVP rows 293-299: BUILD-LOG phase line and Sage SESSIONS line; docs only
e18ad2711 docs: define mixed-format chapter ordering for conversion
12f8c3089 queue: rows 293-299 briefed (revs 3399, 3401-3406, sage) and row 293 claimed by gander (rev 3400, gander-293-start); queue only
253346e3c queue: rows 293-299 added (574 #1805, FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 #1804, 573 #1806; mos-1371/1372), 208 after 294; 270 round 3 request c30929 and Duckworth APPROVE c30930; 270+271 done at 7328da8ca (mos-1351); queue only
7328da8ca gizmoduck: row 270 (264c, #1775) gate results: run 3 red on case1-accept P10 band (Vikunja 573) and run 4 red on the checkout.test fixture race (Vikunja 574), both old tests unchanged in the range; run 5 GREEN on 53406f3e4 under mos-1369 and sage-3106, all phases and 38/38 mutants killed; BUILD.md only
84002f960 gizmoduck: row 270 (264c, #1775) gate run 2 second red, fixed under mos-1364 and sage-3093: compactions-budget.test F1 (:39 input 8.5 * MiB is a whole number, now MiB + 0.5) and F2 (the 0o000 window in try/finally; the after-hook ran after setup's rm); K M22 :129, M23 :120, M22 and M15 notes; BUILD.md run 2
f89aaf57f gizmoduck: row 270 (264c, #1775) gate run 1 red placed (mos-1272): compactions-launch.test's host wires row 271's five clear ops, as session-clear.test does; the clear test's successor never started without them; kill lines +5 in mutants.py K; BUILD.md run 1
d3a49f301 gizmoduck: rows 270+271 combined gate (C2) record before the first run (sage-3073, sage-3086, mos-1275, mos-1351): blob table, timeout table, GREEN and RED, order; code is ac44d0de9's
562b05b43 gizmoduck: row 270 (264c, #1775) round 4 for sage-3073 and sage-3078: rows 270 and 271 rebased onto 25c09f19f (row 203 plus the O2 docs); mosaic-config.mjs keeps 203's federation key and both compaction keys; BUILD.md round 4 with the range-diff, the three merged files and C1 after 203; not run
8c28ee731 gizmoduck: row 270 (264c, #1775) round 3 for duckworth-0226 B4 (sage-3038): one collection opens at most the system config's compactionStreams stream files in name order (default 1024, 32 to 65536, a bad value takes the default and logs), not-collected past it with no cursor for an unopened stream; the missing-stream check uses a Set; readStreams, cap and launcher tests; mutants M22-M24, M10 anchor moved; docs/TOOLS.md has both keys and C1 (N4); BUILD.md round 3 with stream counts, the 231 KB bound, C1 and N5; not run
bce491715 gizmoduck: row 270 (264c, #1775) round 2 for duckworth-0223 B1-B3 (sage-3030, sage-3031, mos-1352, sage-3033): pi streams read in 1 MiB chunks under a budget from the system config's compactionReadBytes (default 64 MiB, 1 MiB to 1 GiB, a bad value takes the default and logs), not-collected past it; Claude history from before the ledger is harness-unsupported for good; launcher, runtime reader, clear and legacy tests; mutants M13-M21; BUILD.md round 2 with sizes, schema change, rollback hazard, N1-N3; not run
01b57f48b gizmoduck: row 270 (264c, #1775) round 1 for review (sage-3024): mutants.py M1-M12 with anchors at 7d199f033, each matching once, kills read from the tests; run plan with broker.mjs reruns; launcher and runtime wiring named as untested; not run
54ec0005e row 271 (264d, #1776): BUILD.md r3 gate on 9e18a9aaf (sage-3017): cli and bus session-clear.test whole green (5/5, 19/19), cli suite 874/874, M14 killed by the T6 test's first lines() assertion at :365; no first-run red. Doc only.
61e262b7c row 271 (264d, #1776): r3 packet carries mutants.py, M1-M14 with their anchors, as the gate applies them (duckworth-0212 C1). Doc and harness only; no src or test change.
540b6f16b row 271 (264d, #1776): r3, T6 per sage-3001. New cli test: a bus unavailable (broker-exited) at the exit proof, then at retirement, holds the clear with no retirement, successor launch or slot handed on, logs once, and resumes when the bus answers. BUILD.md maps the halt case to session-launch.test.mjs:372 and launcher.mjs:824, binds the Q198 hold to row 257 with duckworth-0210's carry, and names M14 (drop clearPass's try/catch) and its kill. No src change. Not run (no CLEAR).
e6a675ee8 row 271 (264d, #1776): BUILD.md gate results on e8af389e6 (sage-2991): 18 touched files, bus/seat/cli suites and every test-*.sh green, M1-M13 killed by assertions in the named tests; the row gate's T6 coverage open to Sage. Doc only.
e77635ceb row 271 (264d, #1776): BUILD.md run limits for the e567f949c gate (mos-1275, sage-2991); mutant lines moved one down past the B1 comment. Doc only.
3fc8ccef1 row 271 (264d, #1776): duckworth-0201 B1: #ownerRefusal passes 'session.clear' to #granted, so the project and instance sit in their own slots and a live collaborator grant passes the retirement recheck. N4 setup shared; a second N4 test keeps the grant and expects retired plus the successor; M13 restores the defect. 264e carry noted at the N4 tests. Not run (no CLEAR).
27d3266ff row 271 (264d, #1776): BUILD.md records mos-1336, Mos's approval under mos-1292 of the later3l, rows() and thread-shares migration test changes on the 3m to 3n rebase, with its conditions
6bd67225b row 271 (264d, #1776): BUILD.md states the post-rebase '3m' grep result as measured
14715d644 row 271 (264d, #1776): r2 on 0cbe2e9b3 (sage-2968): 3m to 3n migration and 3m engine tests from fixtures/schema-3m.sql; BUILD.md records the label change, the old-test changes (later3l, rows() and the thread-shares migration test outside the mos-1315 list, for Mos) and the mutant lines on the rebased tree
f9a9c9b5a row 271 (264d, #1776): BUILD.md defines M1-M12 with their expected kills
34388223d row 271 (264d, #1776): launcher uncertain-path test with test-driven ticks and a stopped runner; start() takes stopTimeoutMs
3adb391b5 row 271 (264d, #1776): N4 failed-clear test on a user's thread with raw first rows; BUILD.md notes the failed path has no verb route yet
4da907cd5 row 271 (264d, #1776): T4 tests round 1, not run (slot held). Bus session-clear.test.mjs (15), cli session-clear.test.mjs (3, bwrap) with clear-adapter.mjs; BUILD.md maps each T4 item and names what is still open (failed clear, launcher uncertain path, mutants)
aa8fc490f row 271 (264d, #1776): clear context build round 1, not run (slot held). Broker: session.clear, session.cancel and session.operation, the fence on send/open/claim, cancel at arrival for routed messages, the drained receive and the end drain, trusted ops sessionClears and sessionClearExited/Uncertain/Retire/Start. Launcher: clearPass before the threads loop (stop, proof, retire, successor start through check()/start()), waiting on a live row 269 survivor. Old tests per mos-1315, rebased on row 206 (sage-2968): label 3m to 3n, future step 3n to 3o, 3m-3n.sql in the engine copies and file lists, ready lines, later3l and the rows() exclusions gain both tables as 206 did for thread_shares (mos-1292, approved in mos-1336), schema.test counts 25 tables and 96 triggers, COUNTED and T45 LISTED gain both tables after thread_shares (mos-1318, mos-1319), TESTER_VERBS pin (mos-1325), fixtures/schema-3m.sql
a9884dc75 row 271 (264d, #1776): schema step 3m to 3n (rebased on row 206's 3l to 3m, sage-2968), session_ops and message_cancels with 7 triggers; successor id pinned as a lowercase version 8 UUID for Huey (gizmoduck-0274)
25c2c1a56 gizmoduck: row 271 design: duckworth-0171 C1 (start stages keyed on a report counter, work attempt in its own column) and C2 (cancelled in the stage CHECK at attempt 0 and closing the op, claim and cancels untouched by a pre-retire cancel, post-retire successor reading stated, race and held tests); N1 grep at build
4f827a5e8 gizmoduck: row 271 design: session.cancel and the cancelled stage built in 271 (sage-2828 re-rules Q3 per D7:263-264), pre- and post-retire paths, cancel tests and the fence-release mutant; N2 refuses session-name-too-long at accept
7dc8ff1bf gizmoduck: row 271 design: T45 LISTED gains session_ops and message_cancels to match COUNTED (mos-1319)
61df3061e gizmoduck: row 271 design: duckworth-0170 B1-B4 folded (cancel at arrival for routed messages, claim refused while clearing, op namespace both ways, attempt key on start stages) and N1-N8 answered; cancel recorded as a departure from D7:263-264
b212182ed gizmoduck: row 271 design: both new tables join COUNTED in stack-release.sh, so T46 passes unchanged; schema.test count pins name the 2 tables and 7 triggers; T45 LISTED question to Mos (mos-1318)
dac64ff31 gizmoduck: row 271 (264d) design draft 2: drained acknowledged by the run's own receive at a turn boundary (a landing turn has unread 0 with a child running), SIGTERM only after it; successor <old>-N bound at request with a deterministic id; no cancel (sage-2826); pre-existing test change list per mos-1315
b329ad69f gizmoduck: row 271 (264d) design draft 1 in the packet: the threadIdle fence as the drain, the idle stop as the only signal, a session_ops journal and message_cancels in one schema step; Q1-Q4 for Sage and Mos
8d4d4ec59 gizmoduck: row 270: the bus legacy test inserts its legacy thread by hand (sessions is append-only) and the test business has the assistant thread.create needs; not run
5ddc36ca9 gizmoduck: row 270 (264c, #1775): completed Pi compactions counted into a host ledger beside the session store, keyed (entry id, timestamp) store-wide (sage-2806, sage-2814); launcher collects at run start and end under the workspace lock; session.list rows gain compactions through a runtime-injected reader; not run (slot held)
f510d7367 gizmoduck: rows 270 and 271 packets: the 264c count and 264d clear contracts pinned for row 272 (Huey), and the Pi 0.85.1 compaction source contract
767bb62c7 docs: close remote credential and private review access findings
6345aae3d docs: align editor graph, Markdown transition and archive with PRD d6
ee5f7b9ef docs: align O2 chapter conversion and Case 2 access with owner rulings
0b923a275 MVP readiness briefs (mos-1371/1372): FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 grant scope #1804, 574 #1805, 573 #1806; docs only
b0a6838ca SESSIONS: Filbert Q195 O2 S0 review (#1798, c30914); doc only
062646487 Q195 O2 W4 (#1799): log lines for the W4 design and review landing, row 203 and O2 landing; docs only
66299eeef Q195 O2 W4 (#1799): Megavolt's round 4 review of 994c3f7fb (blob 2af460ab7): APPROVE, R3-B1 and R3-B2 closed, R4-N1 (re-migration after a pre-W4b restore) non-blocking for W4b; doc only
569bb6b17 Q195 O2 W4 (#1799): Megavolt's round 3 review of 54246cb32 (blob 0c4f55826): CHANGES, R2-B1 projection and R2-B2 closed, R3-B1 (reserved notes keyed on negative IDs) and R3-B2 (fence initial state and confirmation scope undefined); doc only
fb404c909 Q195 O2 W4 (#1799): Megavolt's round 2 review of 019a0ea5b (blob 14dfe92d9): CHANGES, B1-B9 and M1 closed, R2-B1 (paragraph-mark revisions, reserved-note leaves) and R2-B2 (release backup/restore keeps O2 text and revives revocations); doc only
4c0a2c4fd Q195 O2 W4 (#1799): Megavolt's review of the DOCX citation design at 15e91538f (blob 662abdb7c): CHANGES, B1-B9 and M1 (Case 2 narrowing in O2-context turns goes to Mos/Jason); doc only
2f06aad3f docs: define reserved notes and initial O2 restore fence
6863d4c85 docs: specify O2 projection marks and restore revocation fence
9ef5798ac docs: close W4 locator and access review gaps
4897520c3 docs: define committed DOCX citation and access contract
4ca270307 queue: revs 3360-3368. Row 203 (#1701): Sage brief re-pin to 8adfb261f (3360, Webby N2), Quackerjack claim, in-review r2 and outcome (3361-3363), Sage request as jarvis (3364-3365), Webby approve record (3366), Sage landing note and done (3367-3368).
25c09f19f docs: name save-group settings and enforce adoption gates
a6da8718c docs: fix O2 review gates, shipping and save semantics
ee26cfbad docs: redesign Q195 for full WordCraft O2 editor
a4da4e6df row 203: gate results on the rebased tip (c7b754d7b): stack-release 219/0, test-config 51/0, mutants 17/17, cli 895/0, ten scripts green with named skips; code tree unchanged from 7309260ae
7309260ae row 203: gate record on the rebased tip (c7b754d7b): blob table and timeout table, written before the first run
7c543fd4a row 203 FED R2: cases for missing self, peer id and label (webby-0191 B3)
284d55cf2 row 203 FED R2: stack-release probe reads federation; a refusal case per federation check
93a491d73 row 203 FED R2: federation key in the system config (#1701)
c7b754d7b queue: revs 3339-3359. Row 292 (#1797, Gander) added and briefed (Sage, 3339-3340). Row 291: Gosalyn claim and in-review r1 (3341, 3344-3345), Sage request as jarvis (3353-3354), Webby approve record (3357). Row 207: Megavolt in-review (3342), Sage abandon and request as jarvis, resolved on c30841 (3347, 3349-3350), Duckworth approve record (3356), Sage landing note and done (3358-3359). Row 270: Gizmoduck claim and in-review r3 (3343, 3346), Sage abandon and request as jarvis (3348, 3351-3352), Duckworth approve record (3355).
a94c95522 docs: row 207 landing records (SESSIONS, BUILD-LOG, Megavolt's lines), DEFERRED 207 N1 (duckworth-0230) and 270 N6 bound to row 272 (duckworth-0228)
c4f77373f row 207 (#1705): BUILD.md Runs records the gate on 712f79c0a (sage-3041): access.test 31/31, M1-M8 killed on their named tests, cli 894/894, ten scripts green under the mos-1275 limits, and the stopped first run's kill-check correction; doc only
c7e77202d row 207 (#1705): BUILD.md records sage-2810 (Q1 no reload, cited to broker.mjs:384; Q2 refuse with user-has-grants) and the usage/help pin sweep: access.test.mjs is the only reader, no hash pin; doc only
a0094633f row 207 (#1705): access.test.mjs T1-T9 for user add|remove; BUILD.md pins M7/M8 as edits and records the row 275 path check (bus only, morgana/row275 empty); not yet run, waits for Sage's CLEAR (sage-2790)
721268b08 row 207 (#1705): mosaic access user add|remove on R5's path (proof, lock, revision, checkAccess, receipt user.add/user.remove); remove refuses user-has-grants; --level viewer already in R5; BUILD.md records Q1-Q5 (no access.reload, R4's) and the tests and mutants; tests held for the row 275 manifest check (sage-2790)
f0e3dcdf4 plans: brief for the config_checks follow-up (row 270 rollback hazard, duckworth-0226, sage-3038)
01983cd3f queue: rev 3338. Row 224 waits on row 216 (Sage, mos-1351: della/row224 carries row 216's commit 4f3fbc25a).
626c19e78 queue: revs 3325-3337. Row 271: Gizmoduck claim and in-review r3 (3325-3327), Sage review request as jarvis (3328-3329), Duckworth approve record (3330). Row 276: Gander in-review r1 (3331-3332), Sage review request as jarvis (3333-3334), Duckworth approve record (3335), Sage landing note and done (3336-3337).
f32b867f3 docs(row276): record green run5 gate and all seventeen assertion kills
826e8fd7f docs(row276): refresh mos-1350 baseline cleanup packet and limits
e06130fc0 test(case): require baseline session cleanup per mos-1350 review
466562ba0 docs(row276): pin mos-1350 baseline candidate and timing audit
931fab079 perf(case): remove duplicate head validation per mos-1350
135bb8fae test(case): isolate P10 timing with per-mode baseline per mos-1350
cf228eabc docs(row276): record run4 stop and mos-1349 timing pairs
41f8bd197 docs(row276): packet stdout spool and M17 per mos-1345
47906e4fd fix(case): isolate legacy stdout after outer timeout per mos-1345
c2b845c76 docs(row276): preserve run3 whole-file stop for placement
74635fc60 fix(row276): correct multi-file driver path per mos-1337 review
257003ba1 test(row276): record mos-1337 N5 delta and both-copy mutant
537ce2dd4 fix(case): silence vanished stat opens per mos-1337 N5
3b9042802 docs(row276): record run2 stop for Mos placement
859ae3708 docs(row276): void setup-red run and pin first fixture correction
88179d6de test(row276): define read helper for new legacy fixtures
ccfa17eef docs(row276): integrate schema3m base and record dependency drift
b5d9efa1b docs(row276): pin integration on 94c33a43c and preserve upstream docs
69979bb8a docs(row276): record static approval and upstream deferred drift
155f64322 docs(row276): pin supervised-call candidate and fifteen mutants
b96b4e303 fix(case1): supervise legacy user calls across sudo sessions
f2735bb6a queue: revs 3317-3324. Row 206: Launchpad claim and in-review (3317-3319), Sage review request (3320-3321), Duckworth approve record (3322), Sage landing note and done (3323-3324).
bus: scripts/bus-service.sh install --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --pm --claude /home/jwoltje/.local/bin/claude
proof: off
send: off
Console: scripts/webui-service.sh install --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
CLI: scripts/stack-copy.sh wrapper 464010fbe4c6925b077b9b8182a209ee2177eb9d --copies /home/jwoltje/.local/share/mosaic-stack/copies --bin /home/jwoltje/.npm-global/bin
interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
@@ -0,0 +1 @@
launched chief: run r28dbbc5c6b4d, claude-code claude-opus-5-5
@@ -0,0 +1,107 @@
plan: rollback for mosaic-stack
from: 464010fbe4c6925b077b9b8182a209ee2177eb9d
to: 0cbe2e9b35e51fe06dedde69c022b46e040f98c0
store schema: 3n (read from the store the running bus holds)
target schema: 3m
restore: the store must go back to 3m; needs --restore 20261023T034557Z-464010fbe4c6
commits: the run takes away 94 (newest first, at most 200):
464010fbe queue: revs 3435-3436. Row 294 (#1800): Duckworth APPROVE c30998 (3435, candidate 0b3f9c9d). Row 272 (#1777): Scrooge CHANGES c31006 (3436, candidate 0c3dc2ed); queue only
e57bfde73 queue: revs 3407-3434. Row 293 (574, #1805): Gander in-review r1 (3407, failed pre-send 3408), Sage request c30981 (3409/3410), Filbert APPROVE c30982 (3411). Row 292 (#1797): assigned gizmoduck (mos-1387), claim 3413, in-review r1 3419 (3420 failed), Sage request c30992 (3425/3426), Duckworth APPROVE c30975 (3433). Row 294 (#1800): Launchpad claim 3418, in-review r1 3422 (3423 failed), Sage request c30993 (3427/3428). Row 295 (#1801): Morgana claim 3414, in-review r1 3416 (3417 failed), Sage request c30991 resolved (3421/3424), Dewey CHANGES c30994 (3431). Row 272 (#1777): Huey claim 3415, in-review r1 3429 (3430 failed), Sage request c30997 resolved (3432/3434); queue only
319d9f87e sessions: Filbert row 293 (Vikunja 574) APPROVE c30982 rev 3411, plus re-added S0 re-check and 574 static review lines; docs only
b23783f39 Rows 270+271 landing (7328da8ca), 571 landing, MVP rows 293-299: BUILD-LOG phase line and Sage SESSIONS line; docs only
e18ad2711 docs: define mixed-format chapter ordering for conversion
12f8c3089 queue: rows 293-299 briefed (revs 3399, 3401-3406, sage) and row 293 claimed by gander (rev 3400, gander-293-start); queue only
253346e3c queue: rows 293-299 added (574 #1805, FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 #1804, 573 #1806; mos-1371/1372), 208 after 294; 270 round 3 request c30929 and Duckworth APPROVE c30930; 270+271 done at 7328da8ca (mos-1351); queue only
7328da8ca gizmoduck: row 270 (264c, #1775) gate results: run 3 red on case1-accept P10 band (Vikunja 573) and run 4 red on the checkout.test fixture race (Vikunja 574), both old tests unchanged in the range; run 5 GREEN on 53406f3e4 under mos-1369 and sage-3106, all phases and 38/38 mutants killed; BUILD.md only
84002f960 gizmoduck: row 270 (264c, #1775) gate run 2 second red, fixed under mos-1364 and sage-3093: compactions-budget.test F1 (:39 input 8.5 * MiB is a whole number, now MiB + 0.5) and F2 (the 0o000 window in try/finally; the after-hook ran after setup's rm); K M22 :129, M23 :120, M22 and M15 notes; BUILD.md run 2
f89aaf57f gizmoduck: row 270 (264c, #1775) gate run 1 red placed (mos-1272): compactions-launch.test's host wires row 271's five clear ops, as session-clear.test does; the clear test's successor never started without them; kill lines +5 in mutants.py K; BUILD.md run 1
d3a49f301 gizmoduck: rows 270+271 combined gate (C2) record before the first run (sage-3073, sage-3086, mos-1275, mos-1351): blob table, timeout table, GREEN and RED, order; code is ac44d0de9's
562b05b43 gizmoduck: row 270 (264c, #1775) round 4 for sage-3073 and sage-3078: rows 270 and 271 rebased onto 25c09f19f (row 203 plus the O2 docs); mosaic-config.mjs keeps 203's federation key and both compaction keys; BUILD.md round 4 with the range-diff, the three merged files and C1 after 203; not run
8c28ee731 gizmoduck: row 270 (264c, #1775) round 3 for duckworth-0226 B4 (sage-3038): one collection opens at most the system config's compactionStreams stream files in name order (default 1024, 32 to 65536, a bad value takes the default and logs), not-collected past it with no cursor for an unopened stream; the missing-stream check uses a Set; readStreams, cap and launcher tests; mutants M22-M24, M10 anchor moved; docs/TOOLS.md has both keys and C1 (N4); BUILD.md round 3 with stream counts, the 231 KB bound, C1 and N5; not run
bce491715 gizmoduck: row 270 (264c, #1775) round 2 for duckworth-0223 B1-B3 (sage-3030, sage-3031, mos-1352, sage-3033): pi streams read in 1 MiB chunks under a budget from the system config's compactionReadBytes (default 64 MiB, 1 MiB to 1 GiB, a bad value takes the default and logs), not-collected past it; Claude history from before the ledger is harness-unsupported for good; launcher, runtime reader, clear and legacy tests; mutants M13-M21; BUILD.md round 2 with sizes, schema change, rollback hazard, N1-N3; not run
01b57f48b gizmoduck: row 270 (264c, #1775) round 1 for review (sage-3024): mutants.py M1-M12 with anchors at 7d199f033, each matching once, kills read from the tests; run plan with broker.mjs reruns; launcher and runtime wiring named as untested; not run
54ec0005e row 271 (264d, #1776): BUILD.md r3 gate on 9e18a9aaf (sage-3017): cli and bus session-clear.test whole green (5/5, 19/19), cli suite 874/874, M14 killed by the T6 test's first lines() assertion at :365; no first-run red. Doc only.
61e262b7c row 271 (264d, #1776): r3 packet carries mutants.py, M1-M14 with their anchors, as the gate applies them (duckworth-0212 C1). Doc and harness only; no src or test change.
540b6f16b row 271 (264d, #1776): r3, T6 per sage-3001. New cli test: a bus unavailable (broker-exited) at the exit proof, then at retirement, holds the clear with no retirement, successor launch or slot handed on, logs once, and resumes when the bus answers. BUILD.md maps the halt case to session-launch.test.mjs:372 and launcher.mjs:824, binds the Q198 hold to row 257 with duckworth-0210's carry, and names M14 (drop clearPass's try/catch) and its kill. No src change. Not run (no CLEAR).
e6a675ee8 row 271 (264d, #1776): BUILD.md gate results on e8af389e6 (sage-2991): 18 touched files, bus/seat/cli suites and every test-*.sh green, M1-M13 killed by assertions in the named tests; the row gate's T6 coverage open to Sage. Doc only.
e77635ceb row 271 (264d, #1776): BUILD.md run limits for the e567f949c gate (mos-1275, sage-2991); mutant lines moved one down past the B1 comment. Doc only.
3fc8ccef1 row 271 (264d, #1776): duckworth-0201 B1: #ownerRefusal passes 'session.clear' to #granted, so the project and instance sit in their own slots and a live collaborator grant passes the retirement recheck. N4 setup shared; a second N4 test keeps the grant and expects retired plus the successor; M13 restores the defect. 264e carry noted at the N4 tests. Not run (no CLEAR).
27d3266ff row 271 (264d, #1776): BUILD.md records mos-1336, Mos's approval under mos-1292 of the later3l, rows() and thread-shares migration test changes on the 3m to 3n rebase, with its conditions
6bd67225b row 271 (264d, #1776): BUILD.md states the post-rebase '3m' grep result as measured
14715d644 row 271 (264d, #1776): r2 on 0cbe2e9b3 (sage-2968): 3m to 3n migration and 3m engine tests from fixtures/schema-3m.sql; BUILD.md records the label change, the old-test changes (later3l, rows() and the thread-shares migration test outside the mos-1315 list, for Mos) and the mutant lines on the rebased tree
f9a9c9b5a row 271 (264d, #1776): BUILD.md defines M1-M12 with their expected kills
34388223d row 271 (264d, #1776): launcher uncertain-path test with test-driven ticks and a stopped runner; start() takes stopTimeoutMs
3adb391b5 row 271 (264d, #1776): N4 failed-clear test on a user's thread with raw first rows; BUILD.md notes the failed path has no verb route yet
4da907cd5 row 271 (264d, #1776): T4 tests round 1, not run (slot held). Bus session-clear.test.mjs (15), cli session-clear.test.mjs (3, bwrap) with clear-adapter.mjs; BUILD.md maps each T4 item and names what is still open (failed clear, launcher uncertain path, mutants)
aa8fc490f row 271 (264d, #1776): clear context build round 1, not run (slot held). Broker: session.clear, session.cancel and session.operation, the fence on send/open/claim, cancel at arrival for routed messages, the drained receive and the end drain, trusted ops sessionClears and sessionClearExited/Uncertain/Retire/Start. Launcher: clearPass before the threads loop (stop, proof, retire, successor start through check()/start()), waiting on a live row 269 survivor. Old tests per mos-1315, rebased on row 206 (sage-2968): label 3m to 3n, future step 3n to 3o, 3m-3n.sql in the engine copies and file lists, ready lines, later3l and the rows() exclusions gain both tables as 206 did for thread_shares (mos-1292, approved in mos-1336), schema.test counts 25 tables and 96 triggers, COUNTED and T45 LISTED gain both tables after thread_shares (mos-1318, mos-1319), TESTER_VERBS pin (mos-1325), fixtures/schema-3m.sql
a9884dc75 row 271 (264d, #1776): schema step 3m to 3n (rebased on row 206's 3l to 3m, sage-2968), session_ops and message_cancels with 7 triggers; successor id pinned as a lowercase version 8 UUID for Huey (gizmoduck-0274)
25c2c1a56 gizmoduck: row 271 design: duckworth-0171 C1 (start stages keyed on a report counter, work attempt in its own column) and C2 (cancelled in the stage CHECK at attempt 0 and closing the op, claim and cancels untouched by a pre-retire cancel, post-retire successor reading stated, race and held tests); N1 grep at build
4f827a5e8 gizmoduck: row 271 design: session.cancel and the cancelled stage built in 271 (sage-2828 re-rules Q3 per D7:263-264), pre- and post-retire paths, cancel tests and the fence-release mutant; N2 refuses session-name-too-long at accept
7dc8ff1bf gizmoduck: row 271 design: T45 LISTED gains session_ops and message_cancels to match COUNTED (mos-1319)
61df3061e gizmoduck: row 271 design: duckworth-0170 B1-B4 folded (cancel at arrival for routed messages, claim refused while clearing, op namespace both ways, attempt key on start stages) and N1-N8 answered; cancel recorded as a departure from D7:263-264
b212182ed gizmoduck: row 271 design: both new tables join COUNTED in stack-release.sh, so T46 passes unchanged; schema.test count pins name the 2 tables and 7 triggers; T45 LISTED question to Mos (mos-1318)
dac64ff31 gizmoduck: row 271 (264d) design draft 2: drained acknowledged by the run's own receive at a turn boundary (a landing turn has unread 0 with a child running), SIGTERM only after it; successor <old>-N bound at request with a deterministic id; no cancel (sage-2826); pre-existing test change list per mos-1315
b329ad69f gizmoduck: row 271 (264d) design draft 1 in the packet: the threadIdle fence as the drain, the idle stop as the only signal, a session_ops journal and message_cancels in one schema step; Q1-Q4 for Sage and Mos
8d4d4ec59 gizmoduck: row 270: the bus legacy test inserts its legacy thread by hand (sessions is append-only) and the test business has the assistant thread.create needs; not run
5ddc36ca9 gizmoduck: row 270 (264c, #1775): completed Pi compactions counted into a host ledger beside the session store, keyed (entry id, timestamp) store-wide (sage-2806, sage-2814); launcher collects at run start and end under the workspace lock; session.list rows gain compactions through a runtime-injected reader; not run (slot held)
f510d7367 gizmoduck: rows 270 and 271 packets: the 264c count and 264d clear contracts pinned for row 272 (Huey), and the Pi 0.85.1 compaction source contract
767bb62c7 docs: close remote credential and private review access findings
6345aae3d docs: align editor graph, Markdown transition and archive with PRD d6
ee5f7b9ef docs: align O2 chapter conversion and Case 2 access with owner rulings
0b923a275 MVP readiness briefs (mos-1371/1372): FED R6 #1800, FED R4 #1801, FED R7 #1802, GitHub grant method #1803, Q228 grant scope #1804, 574 #1805, 573 #1806; docs only
b0a6838ca SESSIONS: Filbert Q195 O2 S0 review (#1798, c30914); doc only
062646487 Q195 O2 W4 (#1799): log lines for the W4 design and review landing, row 203 and O2 landing; docs only
66299eeef Q195 O2 W4 (#1799): Megavolt's round 4 review of 994c3f7fb (blob 2af460ab7): APPROVE, R3-B1 and R3-B2 closed, R4-N1 (re-migration after a pre-W4b restore) non-blocking for W4b; doc only
569bb6b17 Q195 O2 W4 (#1799): Megavolt's round 3 review of 54246cb32 (blob 0c4f55826): CHANGES, R2-B1 projection and R2-B2 closed, R3-B1 (reserved notes keyed on negative IDs) and R3-B2 (fence initial state and confirmation scope undefined); doc only
fb404c909 Q195 O2 W4 (#1799): Megavolt's round 2 review of 019a0ea5b (blob 14dfe92d9): CHANGES, B1-B9 and M1 closed, R2-B1 (paragraph-mark revisions, reserved-note leaves) and R2-B2 (release backup/restore keeps O2 text and revives revocations); doc only
4c0a2c4fd Q195 O2 W4 (#1799): Megavolt's review of the DOCX citation design at 15e91538f (blob 662abdb7c): CHANGES, B1-B9 and M1 (Case 2 narrowing in O2-context turns goes to Mos/Jason); doc only
2f06aad3f docs: define reserved notes and initial O2 restore fence
6863d4c85 docs: specify O2 projection marks and restore revocation fence
9ef5798ac docs: close W4 locator and access review gaps
4897520c3 docs: define committed DOCX citation and access contract
4ca270307 queue: revs 3360-3368. Row 203 (#1701): Sage brief re-pin to 8adfb261f (3360, Webby N2), Quackerjack claim, in-review r2 and outcome (3361-3363), Sage request as jarvis (3364-3365), Webby approve record (3366), Sage landing note and done (3367-3368).
25c09f19f docs: name save-group settings and enforce adoption gates
a6da8718c docs: fix O2 review gates, shipping and save semantics
ee26cfbad docs: redesign Q195 for full WordCraft O2 editor
a4da4e6df row 203: gate results on the rebased tip (c7b754d7b): stack-release 219/0, test-config 51/0, mutants 17/17, cli 895/0, ten scripts green with named skips; code tree unchanged from 7309260ae
7309260ae row 203: gate record on the rebased tip (c7b754d7b): blob table and timeout table, written before the first run
7c543fd4a row 203 FED R2: cases for missing self, peer id and label (webby-0191 B3)
284d55cf2 row 203 FED R2: stack-release probe reads federation; a refusal case per federation check
93a491d73 row 203 FED R2: federation key in the system config (#1701)
c7b754d7b queue: revs 3339-3359. Row 292 (#1797, Gander) added and briefed (Sage, 3339-3340). Row 291: Gosalyn claim and in-review r1 (3341, 3344-3345), Sage request as jarvis (3353-3354), Webby approve record (3357). Row 207: Megavolt in-review (3342), Sage abandon and request as jarvis, resolved on c30841 (3347, 3349-3350), Duckworth approve record (3356), Sage landing note and done (3358-3359). Row 270: Gizmoduck claim and in-review r3 (3343, 3346), Sage abandon and request as jarvis (3348, 3351-3352), Duckworth approve record (3355).
a94c95522 docs: row 207 landing records (SESSIONS, BUILD-LOG, Megavolt's lines), DEFERRED 207 N1 (duckworth-0230) and 270 N6 bound to row 272 (duckworth-0228)
c4f77373f row 207 (#1705): BUILD.md Runs records the gate on 712f79c0a (sage-3041): access.test 31/31, M1-M8 killed on their named tests, cli 894/894, ten scripts green under the mos-1275 limits, and the stopped first run's kill-check correction; doc only
c7e77202d row 207 (#1705): BUILD.md records sage-2810 (Q1 no reload, cited to broker.mjs:384; Q2 refuse with user-has-grants) and the usage/help pin sweep: access.test.mjs is the only reader, no hash pin; doc only
a0094633f row 207 (#1705): access.test.mjs T1-T9 for user add|remove; BUILD.md pins M7/M8 as edits and records the row 275 path check (bus only, morgana/row275 empty); not yet run, waits for Sage's CLEAR (sage-2790)
721268b08 row 207 (#1705): mosaic access user add|remove on R5's path (proof, lock, revision, checkAccess, receipt user.add/user.remove); remove refuses user-has-grants; --level viewer already in R5; BUILD.md records Q1-Q5 (no access.reload, R4's) and the tests and mutants; tests held for the row 275 manifest check (sage-2790)
f0e3dcdf4 plans: brief for the config_checks follow-up (row 270 rollback hazard, duckworth-0226, sage-3038)
01983cd3f queue: rev 3338. Row 224 waits on row 216 (Sage, mos-1351: della/row224 carries row 216's commit 4f3fbc25a).
626c19e78 queue: revs 3325-3337. Row 271: Gizmoduck claim and in-review r3 (3325-3327), Sage review request as jarvis (3328-3329), Duckworth approve record (3330). Row 276: Gander in-review r1 (3331-3332), Sage review request as jarvis (3333-3334), Duckworth approve record (3335), Sage landing note and done (3336-3337).
f32b867f3 docs(row276): record green run5 gate and all seventeen assertion kills
826e8fd7f docs(row276): refresh mos-1350 baseline cleanup packet and limits
e06130fc0 test(case): require baseline session cleanup per mos-1350 review
466562ba0 docs(row276): pin mos-1350 baseline candidate and timing audit
931fab079 perf(case): remove duplicate head validation per mos-1350
135bb8fae test(case): isolate P10 timing with per-mode baseline per mos-1350
cf228eabc docs(row276): record run4 stop and mos-1349 timing pairs
41f8bd197 docs(row276): packet stdout spool and M17 per mos-1345
47906e4fd fix(case): isolate legacy stdout after outer timeout per mos-1345
c2b845c76 docs(row276): preserve run3 whole-file stop for placement
74635fc60 fix(row276): correct multi-file driver path per mos-1337 review
257003ba1 test(row276): record mos-1337 N5 delta and both-copy mutant
537ce2dd4 fix(case): silence vanished stat opens per mos-1337 N5
3b9042802 docs(row276): record run2 stop for Mos placement
859ae3708 docs(row276): void setup-red run and pin first fixture correction
88179d6de test(row276): define read helper for new legacy fixtures
ccfa17eef docs(row276): integrate schema3m base and record dependency drift
b5d9efa1b docs(row276): pin integration on 94c33a43c and preserve upstream docs
69979bb8a docs(row276): record static approval and upstream deferred drift
155f64322 docs(row276): pin supervised-call candidate and fifteen mutants
b96b4e303 fix(case1): supervise legacy user calls across sudo sessions
f2735bb6a queue: revs 3317-3324. Row 206: Launchpad claim and in-review (3317-3319), Sage review request (3320-3321), Duckworth approve record (3322), Sage landing note and done (3323-3324).
bus: scripts/bus-service.sh install --copy 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 --copies /home/jwoltje/.local/share/mosaic-stack/copies --pm --claude /home/jwoltje/.local/bin/claude
proof: off
send: off
Console: scripts/webui-service.sh install --copy 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 --copies /home/jwoltje/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
CLI: scripts/stack-copy.sh wrapper 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 --copies /home/jwoltje/.local/share/mosaic-stack/copies --bin /home/jwoltje/.npm-global/bin
interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
@@ -0,0 +1,20 @@
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
@@ -0,0 +1 @@
2026-10-23T03:45:57Z
@@ -0,0 +1,14 @@
## W5 activate NEW --pm --migrate --tester-socket (mos-1404)
# start 2026-10-23T03:45:57.740Z
# command: (cd $REPO && <clean env> scripts/stack-release.sh activate 464010fbe4c6925b077b9b8182a209ee2177eb9d mosaic-stack --pm --migrate --tester-socket)
stack-release: interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
stack-release: captured the rollback set: /home/jwoltje/.mosaic-dev/state/stack-rollback/20261023T034557Z-464010fbe4c6
stack-release: stopped both units
stack-release: stopped store schema: 3m
stack-release: backup: /home/jwoltje/.mosaic-dev/backups/stack/20261023T034557Z-464010fbe4c6
stack-release: migrated the store to 3n
stack-release: installed 464010fbe4c6925b077b9b8182a209ee2177eb9d
stack-release: gate passed on 464010fbe4c6925b077b9b8182a209ee2177eb9d
stack-release: activate ok: 464010fbe4c6925b077b9b8182a209ee2177eb9d is live for mosaic-stack
# exit 0, end 2026-10-23T03:46:02.293Z
@@ -0,0 +1,6 @@
types table=26 trigger=97 view=5 index=28
schema_version 3n
missing none
new_table_rows session_ops=0 message_cancels=0 thread_shares 0
events 2202 maxSeq 2202
COUNTS_OK
@@ -0,0 +1,21 @@
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
# exit 0
@@ -0,0 +1 @@
pm run ra38826f3c6e2 (claude-code), launchedBy chief, startedAt 2026-10-23T03:47:35.835Z, pid 3831373, runner pid 3831374: left live by W9b
@@ -0,0 +1,21 @@
B0 (3m) start 2026-10-23T03:41:34.519Z
/home/jwoltje/.mosaic-dev/backups/b0-20261023T034134Z:
total 24
drwx------ 3 jwoltje jwoltje 4096 Oct 22 22:41 .
drwx------ 14 jwoltje jwoltje 4096 Oct 22 22:41 ..
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:41 bus
-rw------- 1 jwoltje jwoltje 37 Oct 22 22:41 CHECK
-rw------- 1 jwoltje jwoltje 3 Oct 22 22:41 SCHEMA
-rw------- 1 jwoltje jwoltje 77 Oct 22 22:41 SHA256SUMS
/home/jwoltje/.mosaic-dev/backups/b0-20261023T034134Z/bus:
total 3008
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:41 .
drwx------ 3 jwoltje jwoltje 4096 Oct 22 22:41 ..
-rw------- 1 jwoltje jwoltje 3072000 Oct 22 22:41 bus.sqlite
SHA256SUMS: 36f3579265b8fbb8c14e8e4734aab073bc9bc128720856799fd98b405221b114 bus.sqlite
SCHEMA: 3m
CHECK: ok events 2200 maxSeq 2200 schema 3m
B0 path /home/jwoltje/.mosaic-dev/backups/b0-20261023T034134Z
B0 SHA256SUMS sha256 6589030ed82a53941e17b44f9010064dac044ec5f720119d37213d9692f16e57
B0 end 2026-10-23T03:41:34.861Z B0_PASS
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), B0 (PLAN.md "Schema and backups"). window-migrate-0cbe2e9b3/b0.sh
# with WIN changed and the expected schema an argument: 3m before the window, 3n before W9b. An online copy of the
# held live store through sqlite's backup API, read-only on the source, as in window-repin-7247d80c7/w12.sh. Laid
# out the way `rehearse --backup PATH` reads it (stack-release.sh:2232-2235 at OLD):
# $DATA/backups/b0-<STAMP>/bus/bus.sqlite the copy, and nothing else in bus/ (rehearse copies bus/*)
# $DATA/backups/b0-<STAMP>/SHA256SUMS written from inside bus/, so it names bus.sqlite
# $DATA/backups/b0-<STAMP>/SCHEMA the copy's meta schema_version, which must read the argument
# $DATA/backups/b0-<STAMP>/CHECK integrity_check, the event count and maxSeq
# Nothing live changes. A failed check stops the script.
# usage: b0.sh 3m|3n
set -uo pipefail
WIN=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n
WANT=${1:?usage: b0.sh 3m|3n}
case $WANT in 3m|3n) ;; *) echo "usage: b0.sh 3m|3n"; exit 2 ;; esac
DATA=$HOME/.mosaic-dev
sha() { sha256sum < "$1" | cut -c1-64; }
stop() { echo "STOP: $*"; exit 1; }
echo "B0 ($WANT) start $(date -u +%FT%T.%3NZ)"
[ -f "$DATA/bus/bus.sqlite" ] || stop "no live store"
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
B=$DATA/backups/b0-$STAMP
[ ! -e "$B" ] || stop "$B exists"
( umask 077; mkdir -p "$B/bus" ) || stop "backup dir"
python3 -B -c '
import sqlite3,sys
src=sqlite3.connect("file:"+sys.argv[1]+"?mode=ro",uri=True)
dst=sqlite3.connect(sys.argv[2])
src.backup(dst)
ok=dst.execute("pragma integrity_check").fetchone()[0]
n=dst.execute("select count(*), max(seq) from events").fetchone()
sv=dst.execute("select value from meta where key=?",("schema_version",)).fetchone()
dst.close(); src.close()
print(ok, "events", n[0], "maxSeq", n[1], "schema", sv[0] if sv else None)
open(sys.argv[3],"w").write((sv[0] if sv else "")+"\n")
sys.exit(0 if ok=="ok" else 1)' "$DATA/bus/bus.sqlite" "$B/bus/bus.sqlite" "$B/SCHEMA" > "$B/CHECK" || stop "backup or integrity_check failed: $(cat "$B/CHECK" 2>/dev/null)"
[ "$(cat "$B/SCHEMA")" = "$WANT" ] || stop "SCHEMA reads $(cat "$B/SCHEMA"), not $WANT"
left=$(ls -A "$B/bus")
[ "$left" = bus.sqlite ] || stop "bus/ holds more than bus.sqlite: $(tr '\n' ' ' <<< "$left")"
chmod 600 "$B/bus/bus.sqlite" "$B/CHECK" "$B/SCHEMA"
( cd "$B/bus" && sha256sum bus.sqlite > ../SHA256SUMS ) || stop "SHA256SUMS"
chmod 600 "$B/SHA256SUMS"
# The same check rehearse makes before it reads the set.
( cd "$B/bus" && sha256sum -c --quiet ../SHA256SUMS ) || stop "the set fails rehearse's SHA256SUMS check"
echo "B0 ($WANT) $(date -u +%FT%TZ) store backup $B ($(cat "$B/CHECK")), SHA256SUMS sha256 $(sha "$B/SHA256SUMS")" >> $WIN/backups.txt
ls -la "$B" "$B/bus"
echo "SHA256SUMS: $(cat "$B/SHA256SUMS")"
echo "SCHEMA: $(cat "$B/SCHEMA")"
echo "CHECK: $(cat "$B/CHECK")"
echo "B0 path $B"
echo "B0 SHA256SUMS sha256 $(sha "$B/SHA256SUMS")"
echo "B0 end $(date -u +%FT%T.%3NZ) B0_PASS"
@@ -0,0 +1,7 @@
D3 2026-10-23T03:41:25Z data-root snapshot /home/jwoltje/.mosaic-dev/backups/d3-20261023T034056Z (STAMP 2026-10-23T03:40:56.998Z, 91612 members, tar exit 0, 0 warning line(s)), SHA256SUMS sha256 6e5cc74553d32e85b4d1d081764ff5bed22b6ac6265ea304913fe0f617f37ee8
BZ 2026-10-23T03:41:29Z business file copy /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261023T034129Z sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93 (source /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93)
B0 (3m) 2026-10-23T03:41:34Z store backup /home/jwoltje/.mosaic-dev/backups/b0-20261023T034134Z (ok events 2200 maxSeq 2200 schema 3m), SHA256SUMS sha256 6589030ed82a53941e17b44f9010064dac044ec5f720119d37213d9692f16e57
W0b 2026-10-23T03:44:07Z business file copy /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261023T034407Z sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93 (source /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93)
D3 2026-10-23T03:44:32Z data-root snapshot /home/jwoltje/.mosaic-dev/backups/d3-20261023T034412Z (STAMP 2026-10-23T03:44:12.165Z, 91652 members, tar exit 0, 0 warning line(s)), SHA256SUMS sha256 eda5be89461d50c49cc3c2f32a03f49d3cd6d3848371e9de2eed183cec7cb74d
W5 2026-10-23T03:46:02Z activation 20261023T034557Z-464010fbe4c6 backup /home/jwoltje/.mosaic-dev/backups/stack/20261023T034557Z-464010fbe4c6 STAMP 2026-10-23T03:45:58.274Z SCHEMA 3m SHA256SUMS sha256 480f5e556d02d3f079c07782af204c0a41e44fb4633064e1d7c302e473be1390
B0 (3n) 2026-10-23T03:47:25Z store backup /home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z (ok events 2202 maxSeq 2202 schema 3n), SHA256SUMS sha256 0946b66ccde18f5be8d49ed9f15cccf09f73804abdf31b3ae9c108c6c9ee9272
@@ -0,0 +1,2 @@
-rw------- 1 jwoltje jwoltje 4304 Oct 19 20:39 /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261023T034129Z
BZ copy /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261023T034129Z sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93 BZ_PASS
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), the business file copy that goes with B0 (as sage-2967 A2). Plain cp and
# sha256sum, as window-migrate-0cbe2e9b3/bz.sh. Nothing reads the file's values.
# usage: bz.sh
set -uo pipefail
WIN=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n
BIZ=$HOME/.config/mosaic-dev/businesses/mosaic-stack.json
sha() { sha256sum < "$1" | cut -c1-64; }
stop() { echo "STOP: $*"; exit 1; }
[ -f "$BIZ" ] || stop "no business file"
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
COPY=$BIZ.pre-window-$STAMP
[ ! -e "$COPY" ] || stop "$COPY exists"
before=$(sha "$BIZ")
cp -p "$BIZ" "$COPY" || stop "cp"
[ "$(sha "$COPY")" = "$before" ] && [ "$(sha "$BIZ")" = "$before" ] || stop "copy sha256 differs, or the file changed during the copy"
echo "BZ $(date -u +%FT%TZ) business file copy $COPY sha256 $(sha "$COPY") (source $BIZ sha256 $before)" >> $WIN/backups.txt
ls -la "$COPY"
echo "BZ copy $COPY sha256 $(sha "$COPY") BZ_PASS"
@@ -0,0 +1,13 @@
# W11 comment text (migrate window 0cbe2e9b3 to 464010fbe). Gitea post: Darkwing. P19 post: Sage, as last window (sage-2425).
## Gitea #1776 (row 271)
Live as of the migrate window to 464010fbe (activation 20261023T034557Z-464010fbe4c6, 2026-10-23 03:46:02Z UTC), Darkwing operating under Q173 with Mos's W5 go (mos-1404). W5 stopped the units, backed up the stopped 3m store and ran 3m-3n.sql in one step. The backup is SCHEMA 3m, SHA256SUMS 480f5e55. The live 3n store has 26 table rows, 97 triggers, 5 views and 28 indexes; session_ops, message_cancels and session_ops_session exist, and both new tables hold 0 rows. NEW's COUNTED list names 16 tables, the two new ones included. The five tester threads read the same after W5 as before, by id, message count and sha256. The Console started after W5 on NEW's copy, so nobody restarted it. The new chief is r28dbbc5c6b4d. W9b (mos-1392) proved a pm launch on NEW: the relay through the chief launched pm run ra38826f3c6e2 (pid 3831373, runner pid 3831374), and its requirements read answered 1.15 and 85 ids. That pm is left live by W9b. P0 to P6, B0, W0 to W6, W9, W9b and W10 passed, with no rollback, restore or prune. STATE report: `agents/darkwing/work/window-migrate-3n/STATE.md`.
## Gitea #1775 (row 270)
Live as of the migrate window to 464010fbe (activation 20261023T034557Z-464010fbe4c6, 2026-10-23 03:46:02Z UTC), Darkwing operating under Q173 with Mos's W5 go (mos-1404). Row 270 landed with 271 in 7328da8ca, and this window put that landing live: the store is 3n and both units run from NEW's copy. W6 read 26 tables, 97 triggers, 5 views and 28 indexes, nothing missing, events 2202. bus-host/sessions.json is sessionsVersion 2 with 1 session, written by NEW's host at 03:46:01Z. The chief r28dbbc5c6b4d and the pm ra38826f3c6e2 that W9b left live both run on NEW; the pm answered its requirements read with 1.15 and 85 ids. The W10 matrix on 7332 equals the pre-window one, line a healthz 200 ok=true. No rollback, restore or prune. STATE report: `agents/darkwing/work/window-migrate-3n/STATE.md`.
## P19 (for Sage)
Migrate window 0cbe2e9b3 to 464010fbe (rows 270 and 271, #1775 and #1776) done on dragon-lin, 2026-10-23, with Darkwing operating under Q173. Activation 20261023T034557Z-464010fbe4c6, pointer at 03:46:02Z UTC. W5 migrated the store 3m to 3n; the backup is SCHEMA 3m, SHA256SUMS 480f5e55. The live store reads 26 tables, 97 triggers, 5 views, 28 indexes, session_ops and message_cancels 0 rows (COUNTED 16), and the tester thread reads match the pre-W5 baseline. The Console on 7332 answers healthz 200 ok=true, and the W10 matrix equals the pre-window one. P0 to P6, B0, W0 to W6, W9, W9b and W10 passed with no rollback, restore or prune. The old chief r148e72c8895d ended with W5's restart; the new chief is r28dbbc5c6b4d. W9b (mos-1392) proved a pm launch on NEW: pm run ra38826f3c6e2 (claude-code), launched by the chief at 03:47:35Z, pid 3831373, runner pid 3831374, left live by W9b. Its requirements read answered 1.15 and 85 ids. The next window's W1 has to expect that pm. STATE: `agents/darkwing/work/window-migrate-3n/STATE.md`.
@@ -0,0 +1,16 @@
D3 start 2026-10-23T03:40:56.998Z
total 1574104
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:41 .
drwx------ 13 jwoltje jwoltje 4096 Oct 22 22:40 ..
-rw------- 1 jwoltje jwoltje 1611837440 Oct 22 22:41 data-root.tar
-rw------- 1 jwoltje jwoltje 57 Oct 22 22:41 MEMBERS
-rw------- 1 jwoltje jwoltje 80 Oct 22 22:41 SHA256SUMS
-rw------- 1 jwoltje jwoltje 25 Oct 22 22:40 STAMP
-rw------- 1 jwoltje jwoltje 0 Oct 22 22:40 WARNINGS
size 1.6G
MEMBERS: members 91612 tar exit 0 sessions.json sessionsVersion 2
WARNINGS: 0 line(s)
D3 path /home/jwoltje/.mosaic-dev/backups/d3-20261023T034056Z
D3 STAMP 2026-10-23T03:40:56.998Z
D3 SHA256SUMS sha256 6e5cc74553d32e85b4d1d081764ff5bed22b6ac6265ea304913fe0f617f37ee8
D3 end 2026-10-23T03:41:25.271Z D3_PASS
@@ -0,0 +1,16 @@
D3 start 2026-10-23T03:44:12.165Z
total 1580208
drwx------ 2 jwoltje jwoltje 4096 Oct 22 22:44 .
drwx------ 15 jwoltje jwoltje 4096 Oct 22 22:44 ..
-rw------- 1 jwoltje jwoltje 1618083840 Oct 22 22:44 data-root.tar
-rw------- 1 jwoltje jwoltje 57 Oct 22 22:44 MEMBERS
-rw------- 1 jwoltje jwoltje 80 Oct 22 22:44 SHA256SUMS
-rw------- 1 jwoltje jwoltje 25 Oct 22 22:44 STAMP
-rw------- 1 jwoltje jwoltje 0 Oct 22 22:44 WARNINGS
size 1.6G
MEMBERS: members 91652 tar exit 0 sessions.json sessionsVersion 2
WARNINGS: 0 line(s)
D3 path /home/jwoltje/.mosaic-dev/backups/d3-20261023T034412Z
D3 STAMP 2026-10-23T03:44:12.165Z
D3 SHA256SUMS sha256 eda5be89461d50c49cc3c2f32a03f49d3cd6d3848371e9de2eed183cec7cb74d
D3 end 2026-10-23T03:44:32.637Z D3_PASS
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), D3: a data-root snapshot (PLAN.md "Schema and backups"), as
# window-migrate-0cbe2e9b3/d3.sh. Plain tar and sha256sum, so it runs outside a slot gap (sage-2970). No node, no python, no
# store read. The live store isn't in it: a tar of a store the bus is writing isn't a consistent copy, so the
# store comes from B0 (before the window) and from W5's own stopped-store backup (after it).
# $DATA/backups/d3-<STAMP>/data-root.tar ~/.mosaic-dev without backups/, bus/bus.sqlite{,-wal,-shm},
# sockets (tar skips sockets), discord/searxng (a container's
# volume, owned by systemd-network, 4.0K) and launches/*/*/tmp
# (old runs' sandbox scratch, which holds write-only test files)
# $DATA/backups/d3-<STAMP>/SHA256SUMS written from inside the directory, names data-root.tar
# $DATA/backups/d3-<STAMP>/STAMP the snapshot's start, from date -u
# $DATA/backups/d3-<STAMP>/WARNINGS tar's stderr; "file changed as we read it" lines go to Mos
# $DATA/backups/d3-<STAMP>/MEMBERS the member count and the checks below
# Nothing live changes. A failed check stops the script.
# usage: d3.sh
set -uo pipefail
umask 077
WIN=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n
DATA=$HOME/.mosaic-dev
sha() { sha256sum < "$1" | cut -c1-64; }
stop() { echo "STOP: $*"; exit 1; }
START=$(date -u +%FT%T.%3NZ)
echo "D3 start $START"
[ -d "$DATA" ] || stop "no data root"
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
D=$DATA/backups/d3-$STAMP
[ ! -e "$D" ] || stop "$D exists"
mkdir -p "$D" || stop "snapshot dir"
echo "$START" > "$D/STAMP"
# --anchored makes each pattern match only that member path, so a rehearsal's own bus.sqlite stays in.
# The last window's first run (d3-20261022T195816Z) stopped on tar exit 2 for 36 unreadable paths under the last
# two excludes; that directory is kept, not removed (prune nothing).
( tar --create --file "$D/data-root.tar" -C "$HOME" --anchored \
--exclude=.mosaic-dev/backups \
--exclude=.mosaic-dev/bus/bus.sqlite \
--exclude=.mosaic-dev/bus/bus.sqlite-wal \
--exclude=.mosaic-dev/bus/bus.sqlite-shm \
--exclude=.mosaic-dev/discord/searxng \
--exclude='.mosaic-dev/launches/*/*/tmp' \
--warning=no-file-ignored \
.mosaic-dev ) 2> "$D/WARNINGS"
rc=$?
# GNU tar: 0 ok, 1 some file changed while read (the archive is still written), 2 fatal.
[ "$rc" -le 1 ] || stop "tar exit $rc: $(head -5 "$D/WARNINGS")"
tar --list --file "$D/data-root.tar" > "$D/.list" || stop "tar --list"
n=$(wc -l < "$D/.list")
bad=$(grep -E '^\.mosaic-dev/(backups/|bus/bus\.sqlite(-wal|-shm)?$|discord/searxng|launches/[^/]+/[^/]+/tmp/)' "$D/.list" | head -3)
[ -z "$bad" ] || stop "excluded member in the archive: $bad"
for m in .mosaic-dev/bus-host/sessions.json .mosaic-dev/state/stack-active.json .mosaic-dev/state/stack-activation-log.jsonl; do
grep -qxF "$m" "$D/.list" || stop "member missing: $m"
done
v=$(tar --extract --to-stdout --file "$D/data-root.tar" .mosaic-dev/bus-host/sessions.json | grep -oE '"sessionsVersion": *[0-9]+' | grep -oE '[0-9]+$')
# 0cbe2e9b3's host wrote v2 at the last window's W5; session.mjs is the same at OLD and NEW, so both read it.
[ "$v" = 2 ] || stop "the snapshot's sessions.json is sessionsVersion ${v:-unreadable}, not 2"
{ echo "members $n"; echo "tar exit $rc"; echo "sessions.json sessionsVersion $v"; } > "$D/MEMBERS"
rm -f "$D/.list"
chmod 600 "$D/data-root.tar" "$D/STAMP" "$D/WARNINGS" "$D/MEMBERS"
( cd "$D" && sha256sum data-root.tar > SHA256SUMS ) || stop "SHA256SUMS"
chmod 600 "$D/SHA256SUMS"
( cd "$D" && sha256sum -c --quiet SHA256SUMS ) || stop "SHA256SUMS check"
echo "D3 $(date -u +%FT%TZ) data-root snapshot $D (STAMP $START, $n members, tar exit $rc, $(wc -l < "$D/WARNINGS") warning line(s)), SHA256SUMS sha256 $(sha "$D/SHA256SUMS")" >> $WIN/backups.txt
ls -la "$D"
echo "size $(du -h "$D/data-root.tar" | cut -f1)"
echo "MEMBERS: $(tr '\n' ' ' < "$D/MEMBERS")"
echo "WARNINGS: $(wc -l < "$D/WARNINGS") line(s)"; head -20 "$D/WARNINGS"
echo "D3 path $D"
echo "D3 STAMP $START"
echo "D3 SHA256SUMS sha256 $(sha "$D/SHA256SUMS")"
echo "D3 end $(date -u +%FT%T.%3NZ) D3_PASS"
@@ -0,0 +1,8 @@
# W10 matrix 2026-10-23T03:48:46Z
a 200 ok=true
b 401 error="sign-in required"
c 401 error="sign-in required"
d 403 error="proxy Host refused"
e 403 error="tester listener required"
f 000 non-json 0 bytes
g 000 non-json 0 bytes
@@ -0,0 +1,7 @@
a 200 ok=true
b 401 error="sign-in required"
c 401 error="sign-in required"
d 403 error="proxy Host refused"
e 403 error="tester listener required"
f 000 non-json 0 bytes
g 000 non-json 0 bytes
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), P0 (PLAN.md P0 row; sage-3135, mos-1385). Git reads and sha256sum
# only, so it runs before the gap. NEW is the local refactor tip at window start. It must be 253346e3c or a
# descendant whose diff from 253346e3c touches no runtime file (only .md, docs/, agents/), and its stack-release.sh
# blob must be 7a8c347b2. The plan sha256 Sage and Mos were sent is the argument; PLAN.md must match it. Writes
# $WIN/NEW and $WIN/PLAN.sha256 once; refuses if either exists.
# usage: p0.sh PLAN_SHA256
set -uo pipefail
REPO=/mnt/storage/src/mosaic-stack
WIN=$REPO/agents/darkwing/work/window-migrate-3n
OLD=0cbe2e9b35e51fe06dedde69c022b46e040f98c0
BASE=253346e3c3917d4c9c0f79bc87b7db6aca73180d
TOOL_BLOB=7a8c347b2be483c8c2d25a2b75b910ae615319a6
WANT=${1:?usage: p0.sh PLAN_SHA256}
stop() { echo "STOP: $*"; exit 1; }
[ ! -e $WIN/NEW ] && [ ! -e $WIN/PLAN.sha256 ] || stop "NEW or PLAN.sha256 already written"
{
echo "# P0 $(date -u +%FT%TZ)"
have=$(sha256sum < $WIN/PLAN.md | cut -c1-64)
echo "plan sha256 $have"
[ "$have" = "$WANT" ] || stop "PLAN.md is not $WANT"
NEW=$(git -C $REPO rev-parse refactor) || stop "rev-parse refactor"
echo "refactor tip $NEW ($(git -C $REPO log -1 --format='%cI %s' $NEW | cut -c1-120))"
git -C $REPO merge-base --is-ancestor $BASE $NEW || stop "253346e3c is not an ancestor of $NEW"
git -C $REPO merge-base --is-ancestor $OLD $NEW || stop "OLD is not an ancestor of $NEW"
run=$(git -C $REPO diff --name-only $BASE $NEW | grep -v -e '\.md$' -e '^docs/' -e '^agents/')
echo "commits past 253346e3c: $(git -C $REPO rev-list --count $BASE..$NEW); runtime paths changed since: ${run:-none}"
[ -z "$run" ] || stop "NEW changes a runtime path past 253346e3c: $(tr '\n' ' ' <<< "$run")"
[ "$(git -C $REPO rev-parse $NEW:scripts/stack-release.sh)" = "$TOOL_BLOB" ] || stop "NEW's stack-release.sh is not 7a8c347b2"
echo "commits OLD..NEW: $(git -C $REPO rev-list --count $OLD..$NEW)"
echo "$NEW" > $WIN/NEW && echo "$WANT" > $WIN/PLAN.sha256 || stop "write"
echo "P0_PASS NEW $NEW"
} 2>&1 | tee -a $WIN/p0.txt
exit ${PIPESTATUS[0]}
@@ -0,0 +1,6 @@
# P0 2026-10-23T03:41:34Z
plan sha256 e664fa3553f9da0bb29905763011223e69c426a56e484ecfd8181743b7628ad1
refactor tip 464010fbe4c6925b077b9b8182a209ee2177eb9d (2026-10-22T22:39:36-05:00 queue: revs 3435-3436. Row 294 (#1800): Duckworth APPROVE c30998 (3435, candidate 0b3f9c9d). R)
commits past 253346e3c: 6; runtime paths changed since: none
commits OLD..NEW: 94
P0_PASS NEW 464010fbe4c6925b077b9b8182a209ee2177eb9d
@@ -0,0 +1,21 @@
// Migrate window 0cbe2e9b3 to NEW (3m to 3n) (PLAN.md), P1: the live business file, read only. Prints the plan's P1 values and nothing else:
// bus.tester, the plug-in lists, launch.by, whether launch.instances includes assistant, and each role's
// vars.harness and vars["fallback.harness"]. No other value from the file.
import { readFileSync } from 'node:fs';
const f = `${process.env.HOME}/.config/mosaic-dev/businesses/mosaic-stack.json`;
const b = JSON.parse(readFileSync(f, 'utf8'));
console.log(`bus.tester ${JSON.stringify(b.vars?.['bus.tester'])}`);
const p = b.plugins ?? {};
console.log(`plugins kinds ${JSON.stringify(Object.keys(p).sort())}`);
for (const k of ['usage-source', 'channel', 'harness']) console.log(`plugins ${k} ${JSON.stringify(p[k])}`);
console.log(`launch.by ${JSON.stringify(b.launch?.by)}`);
const inst = b.launch?.instances;
const names = Array.isArray(inst) ? inst.map((i) => (typeof i === 'string' ? i : i?.name)) : Object.keys(inst ?? {});
console.log(`launch.instances includes assistant ${names.includes('assistant')}`);
let pi = 0;
for (const [r, v] of Object.entries(b.roles ?? {}).sort()) {
const h = v?.vars?.harness, fh = v?.vars?.['fallback.harness'];
if (h === 'pi' || fh === 'pi') pi++;
console.log(`role ${r} vars.harness ${JSON.stringify(h)}${fh === undefined ? '' : ` fallback.harness ${JSON.stringify(fh)}`}`);
}
console.log(`roles naming pi ${pi}`);
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), P1 (PLAN.md P1 row; sage-3135). Read only; nothing stopped.
# business-check.mjs runs from the repo copy, checked against SCRIPTS.sha256. From the system config this
# prints dataRoot and the top-level key names only. Row 270's C1: OLD's loader (0cbe2e9b3's mosaic-config.mjs:246)
# accepts configVersion, environment, dataRoot, execution and console and refuses any other key, so federation,
# compactionReadBytes or compactionStreams on disk would break a rollback. Either one, or a bus.tester other than
# true, stops the window here. A console key is recorded, not stopped on: both loaders accept it.
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
S=$REPO/agents/sage/work/window-plan/repin-de352dfaf BC=$S/business-check.mjs
B=$HOME/.config/mosaic-dev/businesses/mosaic-stack.json
{ echo "# P1 start $(date -u +%FT%T.%3NZ), REPO HEAD $(git -C $REPO rev-parse HEAD)"
echo "# plan sha256 $(sha $PLAN), P0 recorded $PLAN_SHA; NEW $NEW"
echo "# business-check.mjs sha256 $(sha256sum < $BC | cut -c1-64); SCRIPTS.sha256 line: $(grep ' business-check.mjs$' $S/SCRIPTS.sha256)"
echo "# business file sha256 $(sha256sum < $B | cut -c1-64), mtime $(stat -c %y $B)"; } | tee -a $WIN/P1.txt
(cd $S && grep ' business-check.mjs$' SCRIPTS.sha256 | sha256sum -c -) || { echo "STOP business-check.mjs sha mismatch" | tee -a $WIN/P1.txt; exit 1; }
$WIN/w.sh P1.txt "P1 status" scripts/stack-release.sh status mosaic-stack
$WIN/w.sh P1.txt "P1 values" node $WIN/p1.mjs
grep -qx 'bus.tester true' <(node $WIN/p1.mjs) || { echo "STOP bus.tester is not true" | tee -a $WIN/P1.txt; exit 2; }
$WIN/w.sh P1.txt "P1 business-check OLD" node $BC $OLD
[ "$(sha $PLAN)" = "$PLAN_SHA" ] || { echo "STOP plan sha256 is not P0's" | tee -a $WIN/P1.txt; exit 1; }
KEYS=$(node -e 'const c=JSON.parse(require("fs").readFileSync(process.env.HOME+"/.config/mosaic-dev/config.json","utf8"));process.stdout.write(Object.keys(c).sort().join(" "))')
{ echo; echo "## P1 config.json top-level key names (C1; names only)"; echo "$KEYS"; } | tee -a $WIN/P1.txt
for k in federation compactionReadBytes compactionStreams; do
grep -qw -- "$k" <<< "$KEYS" && { echo "STOP config.json holds $k (C1; OLD's loader refuses it, so a rollback would fail)" | tee -a $WIN/P1.txt; exit 3; }
done
echo "# C1: none of federation, compactionReadBytes, compactionStreams; console key $(grep -qw console <<< "$KEYS" && echo present || echo absent)" | tee -a $WIN/P1.txt
DATA=$(node -e 'const c=JSON.parse(require("fs").readFileSync(process.env.HOME+"/.config/mosaic-dev/config.json","utf8"));process.stdout.write(String(c.dataRoot))')
DATA=${DATA/#\~/$HOME}
{ echo; echo "## P1 data console"; echo "# dataRoot $DATA"
echo "# ls -ld \$DATA/console:"; ls -ld "$DATA/console" 2>&1; echo "# ls rc=$?"
sock="$DATA/console/tester.sock"; echo "# tester.sock path length $(printf %s "$sock" | wc -c) bytes (limit 107)"
echo "# P1 end $(date -u +%FT%T.%3NZ)"; } | tee -a $WIN/P1.txt
@@ -0,0 +1,49 @@
# P2 unit diffs 2026-10-23T03:42:00Z, REPO HEAD 464010fbe; webui-service.sh and bus-service.sh blobs equal NEW's (P5)
## webui: scripts/webui-service.sh render --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
# render rc=0
2c2
< # Rendered by scripts/webui-service.sh from packages/webui/systemd/; /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0,
---
> # Rendered by scripts/webui-service.sh from packages/webui/systemd/; /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d,
4c4
< # /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0 is the read-only copy `install --copy ID` names, and the flags
---
> # /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d is the read-only copy `install --copy ID` names, and the flags
18c18
< Documentation=file:///home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/packages/webui/README.md
---
> Documentation=file:///home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d/packages/webui/README.md
24c24
< WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
---
> WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
29c29
< ExecStart=/usr/bin/node /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/packages/webui/src/cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
---
> ExecStart=/usr/bin/node /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d/packages/webui/src/cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
# diff live vs render rc=1
## webui diff with the copy path normalized (OLD id -> NEW id in the live unit)
# normalized diff rc=0
## bus: scripts/bus-service.sh render --copy 464010fbe4c6925b077b9b8182a209ee2177eb9d --pm --claude /home/jwoltje/.local/bin/claude
# render rc=0
2c2
< # Rendered by scripts/bus-service.sh from packages/cli/systemd/; /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0,
---
> # Rendered by scripts/bus-service.sh from packages/cli/systemd/; /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d,
8c8
< Documentation=file:///home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/packages/cli/README.md
---
> Documentation=file:///home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d/packages/cli/README.md
14c14
< WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
---
> WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
30c30
< ExecStart=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/scripts/mosaic bus start %i --pm
---
> ExecStart=/home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d/scripts/mosaic bus start %i --pm
# diff live vs render rc=1
## bus diff with the copy path normalized (OLD id -> NEW id in the live unit)
# normalized diff rc=0
# end 2026-10-23T03:42:00Z rc=0
P2_UNITS_PASS
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), P2 stub runs (PLAN.md P2 row), as window-migrate-0cbe2e9b3/p2.sh. Launch from /.
# Run 1: console-stub.mjs (NEW's startServer, no tester), console-check.sh NEW PORT. Run 2: console-stub-tester.mjs
# adds NEW's startTesterSocket on $D/console/tester.sock under a fresh scratch data root; console-check.sh again,
# then the socket checks. Both stubs must report CALLS [] on SIGTERM. console-check.sh reads NEW's copy under
# the real HOME (W0 made it there) and only reads. Kills only the stub pids it started.
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
S=$REPO/agents/sage/work/window-plan/repin-de352dfaf
C=$HOME/.local/share/mosaic-stack/copies/$NEW
[ -n "${TMPDIR:-}" ] && [[ "$TMPDIR" == /mnt/storage/scratch/tmp/* ]] || { echo "STOP TMPDIR must be under /mnt/storage/scratch/tmp"; exit 9; }
echo "start $(date -u +%FT%TZ)"
(cd $S && grep -E ' (console-check.sh)$' SCRIPTS.sha256 | sha256sum -c -) || { echo "STOP console-check.sh sha"; exit 1; }
(cd $S/fixture && sha256sum -c SHA256SUMS) || { echo "STOP fixture sha"; exit 1; }
rc=0
start_stub() { # out-file, then node args; sets spid and port
local o="$1"; shift
node "$@" > "$o.out" 2> "$o.err" &
spid=$!
for _ in $(seq 1 50); do grep -q '^PORT ' "$o.out" && break; sleep 0.1; done
port=$(sed -n 's/^PORT //p' "$o.out")
[ -n "$port" ] || { echo "STOP stub did not start"; cat "$o.err"; kill -TERM $spid 2>/dev/null; wait $spid 2>/dev/null; exit 1; }
}
stop_stub() { kill -TERM $spid; wait $spid 2>/dev/null; cat "$1.out"; grep -qx 'CALLS \[\]' "$1.out" && echo "PASS CALLS []" || { echo "FAIL CALLS not []"; rc=1; }; }
echo "## run 1: console-stub.mjs, no tester"
O1=$TMPDIR/p2-run1
start_stub $O1 $S/fixture/console-stub.mjs $C
echo "stub pid $spid port $port"
out=$(bash $S/console-check.sh $NEW $port); r=$?; echo "$out"; echo "console-check rc=$r"; [ $r = 0 ] || rc=1
stop_stub $O1
echo "## run 2: console-stub-tester.mjs with --tester-socket's listener under a scratch data root"
D=$(mktemp -d /mnt/storage/scratch/tmp/p2tXXXXXX); echo "data root $D"
O2=$TMPDIR/p2-run2
node $S/fixture/console-stub-tester.mjs $C $D > $O2.out 2> $O2.err &
spid=$!
for _ in $(seq 1 50); do grep -q '^SOCK ' $O2.out && break; sleep 0.1; done
port=$(sed -n 's/^PORT //p' $O2.out); sock=$(sed -n 's/^SOCK //p' $O2.out)
[ -n "$port" ] && [ -n "$sock" ] || { echo "STOP tester stub did not start"; cat $O2.err; kill -TERM $spid 2>/dev/null; wait $spid 2>/dev/null; exit 1; }
echo "stub pid $spid port $port sock $sock"
out=$(bash $S/console-check.sh $NEW $port); r=$?; echo "$out"; echo "console-check rc=$r"; [ $r = 0 ] || rc=1
[ "$sock" = "$D/console/tester.sock" ] && echo "PASS SOCK is \$D/console/tester.sock" || { echo "FAIL SOCK $sock"; rc=1; }
st=$(stat -c '%F %a' "$sock"); [ "$st" = "socket 600" ] && echo "PASS sock: $st" || { echo "FAIL sock: $st"; rc=1; }
dt=$(stat -c '%F %a' "$D/console"); [ "$dt" = "directory 700" ] && echo "PASS console dir: $dt" || { echo "FAIL console dir: $dt"; rc=1; }
code=$(curl -s -o /dev/null -w '%{http_code}' --unix-socket "$sock" http://mosaic-tester.invalid/api/config)
[ "$code" = 200 ] && echo "PASS tester /api/config over the socket: 200" || { echo "FAIL tester /api/config: $code"; rc=1; }
stop_stub $O2
[ ! -e "$sock" ] && echo "PASS socket gone after SIGTERM" || { echo "FAIL socket still there"; rc=1; }
[ -s $O1.err ] || [ -s $O2.err ] && { echo "# stub stderr:"; cat $O1.err $O2.err; }
left=0; for p in /proc/[0-9]*; do c=$(tr '\0' ' ' < $p/cmdline 2>/dev/null); [[ "$c" == *fixture/console-stub* ]] && { echo "LEFT ${p#/proc/} $c"; left=1; }; done
[ $left = 0 ] && echo "sweep: no console-stub process left"
echo "end $(date -u +%FT%TZ) rc=$rc"; [ $rc = 0 ] && echo P2_STUBS_PASS || echo P2_STUBS_FAIL
exit $rc
@@ -0,0 +1,38 @@
start 2026-10-23T03:41:59Z
console-check.sh: OK
console-stub.mjs: OK
console-stub-tester.mjs: OK
## run 1: console-stub.mjs, no tester
stub pid 3772158 port 44245
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa length=4 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
console-check rc=0
PORT 44245
CALLS []
PASS CALLS []
## run 2: console-stub-tester.mjs with --tester-socket's listener under a scratch data root
data root /mnt/storage/scratch/tmp/p2t2B6RcZ
stub pid 3772267 port 42563 sock /mnt/storage/scratch/tmp/p2t2B6RcZ/console/tester.sock
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa length=4 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
console-check rc=0
PASS SOCK is $D/console/tester.sock
PASS sock: socket 600
PASS console dir: directory 700
PASS tester /api/config over the socket: 200
PORT 42563
SOCK /mnt/storage/scratch/tmp/p2t2B6RcZ/console/tester.sock
CALLS []
PASS CALLS []
PASS socket gone after SIGTERM
sweep: no console-stub process left
end 2026-10-23T03:42:00Z rc=0
P2_STUBS_PASS
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), P2 second half (PLAN.md P2 row): render both units from REPO's service
# scripts for NEW with the pointer's flags, diff against the live units, then diff again with the live copy id
# swapped OLD to NEW. The only difference allowed is the copy path, so the swapped diff must be empty.
# Output follows last window's p2-units.txt. Render writes nothing and starts nothing; it only looks up node's path.
# usage: p2units.sh > p2-units.txt
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
U=${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user
rc=0
cd $REPO || exit 1
git diff --quiet $NEW HEAD -- scripts/webui-service.sh scripts/bus-service.sh || { echo "STOP service scripts differ from NEW"; exit 1; }
echo "# P2 unit diffs $(date -u +%FT%TZ), REPO HEAD $(git rev-parse --short=9 HEAD); webui-service.sh and bus-service.sh blobs equal NEW's (P5)"
one() { # name, live unit file, render command...
local name=$1 live=$2; shift 2
local r; r=$(mktemp /mnt/storage/scratch/tmp/p2uXXXXXX)
echo "## $name: $*"
"$@" > "$r"; local x=$?; echo "# render rc=$x"; [ $x = 0 ] || rc=1
diff "$live" "$r"; echo "# diff live vs render rc=$?"
echo "## $name diff with the copy path normalized (OLD id -> NEW id in the live unit)"
sed "s/$OLD/$NEW/g" "$live" | diff - "$r"; x=$?; echo "# normalized diff rc=$x"; [ $x = 0 ] || rc=1
rm -f "$r"
}
one webui $U/mosaic-webui.service scripts/webui-service.sh render --copy $NEW --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket
one bus $U/[email protected] scripts/bus-service.sh render --copy $NEW --pm --claude $HOME/.local/bin/claude
echo "# end $(date -u +%FT%TZ) rc=$rc"; [ $rc = 0 ] && echo P2_UNITS_PASS || echo P2_UNITS_FAIL
exit $rc
@@ -0,0 +1,29 @@
decision_events: 22 -> 22
decisions: 22 -> 22
deliveries: 1935 -> 1935
events: 2200 -> 2200
gitea_grants: 0 -> 0
gitea_users: 0 -> 0
host_grants: 0 -> 0
hosts: 0 -> 0
instance_names: 0 -> 0
message_cancels: None -> 0 CHANGED
message_threads: 10 -> 10
messages: 635 -> 635
meta: 3 -> 3
projects: 5 -> 5
role_claims: 115 -> 115
session_defaults: 5 -> 5
session_ops: None -> 0 CHANGED
sessions: 5 -> 5
sqlite_sequence: 14 -> 14
task_comment_cursors: 666 -> 666
task_snapshots: 577 -> 577
thread_claims: 10 -> 10
thread_opens: 0 -> 0
thread_shares: 0 -> 0
threads: 5 -> 5
usage_records: 104 -> 104
lookalike_action_allowed backup 0 rehearsal 0
any_grant_event backup 0 rehearsal 0
rc=0
@@ -0,0 +1,16 @@
import sqlite3, sys, json
def counts(p):
c = sqlite3.connect(f"file:{p}?mode=ro", uri=True)
t = [r[0] for r in c.execute("select name from sqlite_master where type='table' order by name")]
out = {n: c.execute(f'select count(*) from "{n}"').fetchone()[0] for n in t}
la = c.execute("""select count(*) from events where json_extract(body,'$.action')='action.allowed' and (
coalesce(json_extract(body,'$.body.action'),'') like 'grant.%' or coalesce(json_extract(body,'$.body.operation'),'') like 'grant.%'
or instr(body,'hostLineLeft')>0)""").fetchone()[0] if 'events' in t else None
la2 = c.execute("""select count(*) from events where (json_extract(body,'$.action') like 'grant.%' or json_extract(body,'$.operation') like 'grant.%' or instr(body,'hostLineLeft')>0)""").fetchone()[0]
return out, la, la2
a, laa, la2a = counts(sys.argv[1]); b, lab, la2b = counts(sys.argv[2])
for k in sorted(set(a) | set(b)):
mark = '' if a.get(k) == b.get(k) else ' CHANGED'
print(f"{k}: {a.get(k)} -> {b.get(k)}{mark}")
print("lookalike_action_allowed backup", laa, "rehearsal", lab)
print("any_grant_event backup", la2a, "rehearsal", la2b)
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), P3 hand check: start the rehearsal store's host (--no-launch) and a
# Console on 7341 from NEW's copy against the rehearsal config, read healthz, /api/config and the tester socket,
# then stop both and count the receipts. Touches only the rehearsal dir P3 made.
# usage: p3/hand-console.sh REHEARSAL_DIR
set -u
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
R=${1:?usage: p3/hand-console.sh REHEARSAL_DIR}
case "$R" in "$DATA"/rehearsals/*-"${NEW:0:12}") ;; *) echo "STOP: $R is not a rehearsal dir for NEW"; exit 1 ;; esac
[ -f "$R/config/config.json" ] || { echo "STOP: no $R/config/config.json"; exit 1; }
C=$HOME/.local/share/mosaic-stack/copies/$NEW
[ -d "$C" ] || { echo "STOP: no copy for NEW at $C"; exit 1; }
E=(env -i HOME="$HOME" PATH="$PATH" MOSAIC_CONFIG="$R/config/config.json")
date -u +%FT%TZ
echo "receipt lines before: $(wc -l < "$R/data/bus/peercred-starts.jsonl")"
"${E[@]}" "$C/scripts/mosaic" bus start mosaic-stack --no-launch > "$R/hand-host.log" 2>&1 & HP=$!
for i in $(seq 1 30); do [ -S "$R/data/bus/broker.sock" ] && break; sleep 0.5; done
"${E[@]}" node "$C/packages/webui/src/cli.mjs" serve --port 7341 --business mosaic-stack --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket > "$R/hand-console.log" 2>&1 & CP=$!
for i in $(seq 1 30); do curl -s -o /dev/null http://127.0.0.1:7341/healthz && [ -S "$R/data/console/tester.sock" ] && break; sleep 0.5; done
echo "host pid $HP console pid $CP"
echo "healthz: $(curl -s -w ' %{http_code}' http://127.0.0.1:7341/healthz)"
echo "api/config: $(curl -s -w ' %{http_code}' http://127.0.0.1:7341/api/config)"
echo "api/config Host public: $(curl -s -w ' %{http_code}' -H 'Host: mosaic.woltje.com' http://127.0.0.1:7341/api/config)"
stat -c 'socket %a %U %n' "$R/data/console/tester.sock"; stat -c 'dir %a %U %n' "$R/data/console"
echo "tester GET /api/config: $("${E[@]}" node "$C/packages/cli/src/tester-http.mjs" GET /api/config 2>&1; echo " rc=$?")"
echo "tester GET /api/bus/agents: $("${E[@]}" node "$C/packages/cli/src/tester-http.mjs" GET /api/bus/agents 2>&1 | cut -c1-400; echo " rc=${PIPESTATUS[0]}")"
kill -TERM "$CP"; wait "$CP"; echo "console exit $?"
kill -TERM "$HP"; wait "$HP"; echo "host exit $?"
[ -e "$R/data/bus/writer.lock" ] && echo "writer.lock present" || echo "writer.lock released"
echo "receipt lines after: $(wc -l < "$R/data/bus/peercred-starts.jsonl")"
echo "library lines: $(grep -c 'library:' "$R/hand-host.log")"
date -u +%FT%TZ
@@ -0,0 +1,26 @@
PASS copy: 464010fbe4c6925b077b9b8182a209ee2177eb9d, schema 3n
PASS port: 127.0.0.1:7341 is free
PASS backup: /home/jwoltje/.mosaic-dev/backups/b0-20261023T034134Z matches its SHA256SUMS
PASS scratch-root: /home/jwoltje/.mosaic-dev/rehearsals/20261023T034320Z-464010fbe4c6/data from the backup, config and business file under /home/jwoltje/.mosaic-dev/rehearsals/20261023T034320Z-464010fbe4c6/config, business.diff beside them
PASS data-root-live: /home/jwoltje/.mosaic-dev/rehearsals/20261023T034320Z-464010fbe4c6/data is not the live data root
PASS sessions-live: no running session named in the data root
PASS data-root-scratch
PASS notify-off: binding null, so no Discord bridge
PASS tracker-configured: bootConfig sets no trackers
PASS credential-ref-live: 9 reference(s), each a placeholder
BACKUP_STORE /home/jwoltje/.mosaic-dev/backups/b0-20261023T034134Z schema 3m
PASS new-refuses-old-store: 464010fbe4c6925b077b9b8182a209ee2177eb9d refuses the 3m store at a normal open
PASS migrate: 3m to 3n
PASS new-opens-migrated
PASS migrate-again: a second run is a no-op
PASS old-refuses-migrated: 0cbe2e9b35e51fe06dedde69c022b46e040f98c0 refuses the 3n store
PASS gate: host-up, bus-status, console-up, healthz and copy-match on 464010fbe4c6925b077b9b8182a209ee2177eb9d
PASS no-session: no sessions.json and no launches under /home/jwoltje/.mosaic-dev/rehearsals/20261023T034320Z-464010fbe4c6/data
PASS console-stopped: pid 3785792
PASS host-stopped: pid 3785790
PASS host-released-store
PASS backup-unchanged
PASS live-config-unchanged
REHEARSAL_PASS
rehearsal directory: /home/jwoltje/.mosaic-dev/rehearsals/20261023T034320Z-464010fbe4c6
rc=0
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# P4-P6 of agents/darkwing/work/window-migrate-3n/PLAN.md, from window-migrate-0cbe2e9b3/p456.sh, read only.
# P4 the tunnel unit, P5 the four REPO scripts against NEW, P6 the 7332 matrix on OLD (matrix-old.txt).
# usage: p456.sh p4|p5|p6|tester
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
FILES=("${FOUR[@]}")
# The plan's <32 letters>: a fixed dummy, not a secret. matrix-new.txt uses the same value.
PROOF=abcdefghijklmnopqrstuvwxyzabcdef
p4() {
echo "# P4 $(date -u +%FT%TZ)"
local out; out="$(systemctl --user show mosaic-preview-tunnel.service -p ExecStart -p DropInPaths)"; local rc=$?
printf '%s\n' "$out"; echo "# exit $rc"
local specs; specs="$(grep -oE 'R:[^ ;]+' <<<"$out")"
local n; n="$(grep -c . <<<"$specs")"
echo "# R: specs ($n): $(tr '\n' ' ' <<<"$specs")"
local drop; drop="$(sed -n 's/^DropInPaths=//p' <<<"$out")"
if [[ $rc -eq 0 && $n -eq 1 && "$specs" == "R:0.0.0.0:7332:127.0.0.1:7332" && -z "$drop" ]]; then
echo "P4 PASS: one R: spec R:0.0.0.0:7332:127.0.0.1:7332, DropInPaths empty"
else
echo "P4 FAIL"; return 3
fi
}
p5() {
echo "# P5 $(date -u +%FT%TZ); HEAD $(git -C $REPO rev-parse HEAD)"
git -C $REPO diff --quiet $NEW HEAD -- "${FILES[@]}"; local d=$?
echo "# git diff --quiet $NEW HEAD -- ${FILES[*]}: exit $d"
local st; st="$(git -C $REPO status --porcelain -- "${FILES[@]}")"; local s=$?
echo "# git status --porcelain (exit $s): [${st}]"
for f in "${FILES[@]}"; do echo "# $f HEAD blob $(git -C $REPO rev-parse HEAD:$f) NEW blob $(git -C $REPO rev-parse $NEW:$f)"; done
if [[ $d -eq 0 && $s -eq 0 && -z "$st" ]]; then echo "P5 PASS"; else echo "P5 FAIL"; return 3; fi
}
# one matrix line: label, then curl arguments; prints "<label> <code> <error or ok field>"
req() {
local label="$1"; shift
local body; body="$(mktemp -p "${TMPDIR:-/tmp}")"
local code; code="$(curl -s -m 10 -o "$body" -w '%{http_code}' "$@")"
local field; field="$(node -e '
let s = require("fs").readFileSync(process.argv[1], "utf8"), j;
try { j = JSON.parse(s); } catch { console.log("non-json " + s.length + " bytes"); process.exit(0); }
console.log(j.error !== undefined ? "error=" + JSON.stringify(j.error) : "ok=" + JSON.stringify(j.ok));
' "$body")"
rm -f "$body"
echo "$label $code $field"
}
p6() {
req a http://127.0.0.1:7332/healthz
req b -H 'Host: mosaic.woltje.com' http://127.0.0.1:7332/
req c -H 'Host: mosaic.woltje.com' -H "X-Mosaic-Proof: $PROOF" http://127.0.0.1:7332/
req d -H 'X-authentik-username: tester-probe' http://127.0.0.1:7332/
req e -H 'x-mosaic-tester: 1' http://127.0.0.1:7332/
req f -H 'X-authentik-username: tester-probe' http://127.0.0.1:7333/
req g -H "X-Mosaic-Proof: $PROOF" http://127.0.0.1:7333/
}
# The two tester reads on OLD, through OLD's tester-http.mjs on $DATA/console/tester.sock (7333 has no listener
# since row 196; DEVIATIONS item 1 of the 86f95bb27 window), for W10's reads through NEW's to compare against.
# OLD comes from vars.sh: 0cbe2e9b3, the live copy.
# Same line shape as w10.sh: status, byte count and sha256 of the body, and for /api/config the whole body
# (board, tester, send, edit). /api/bus/agents prints its top-level shape and the agent count only.
tread() {
local path="$1" out rc
out=$(node $HOME/.local/share/mosaic-stack/copies/$OLD/packages/cli/src/tester-http.mjs GET "$path"); rc=$?
printf '%s' "$out" | node -e '
const r=JSON.parse(require("fs").readFileSync(0,"utf8")),s=r.text,h=require("crypto").createHash("sha256").update(s).digest("hex");
const j=r.body,shape=Array.isArray(j)?`array(${j.length})`:j?`keys ${JSON.stringify(Object.keys(j))}`:"non-json";
const list=j&&(Array.isArray(j)?j:Array.isArray(j.rows)?j.rows:null);
console.log(`${process.argv[1]} ${r.status} ${Buffer.byteLength(s)} bytes sha256 ${h.slice(0,16)} ${shape}`+
(process.argv[1]==="/api/config"?` body ${s.trim()}`:list?` agents ${list.length}`:""));' "$path" 2>&1
echo "# exit $rc"
}
case "${1:-}" in
p4) p4 ;;
p5) p5 ;;
p6) p6 ;;
tester) echo "# P6 tester reads on OLD through the socket $(date -u +%FT%TZ)"; tread /api/config; tread /api/bus/agents ;;
*) echo "usage: p456.sh p4|p5|p6|tester" >&2; exit 2 ;;
esac
@@ -0,0 +1,2 @@
STOP: login is not darkwing
exit 1
@@ -0,0 +1,2 @@
STOP: login is not darkwing
exit 1
@@ -0,0 +1,4 @@
login darkwing ok
#1776 comment 31009 posted, read back equal 2026-10-23T03:52:34Z
#1775 comment 31010 posted, read back equal 2026-10-23T03:52:35Z
exit 0
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n) W11: post comments.md's Gitea body on each issue named (the PLAN's W11
# row names #1776 for 271's 3m-3n.sql and #1775 for 270) as darkwing, and read each back. Stops at the first miss.
# usage: post-gitea.sh N [N ...]
set -uo pipefail
REPO=/mnt/storage/src/mosaic-stack
WIN=$REPO/agents/darkwing/work/window-migrate-3n
[ $# -ge 1 ] || { echo "usage: post-gitea.sh N [N ...]"; exit 2; }
export MOSAIC_GITEA_CREDENTIAL_FILE=$HOME/.mosaic/fleet/agents/darkwing/secrets/gitea-mosaicstack-darkwing.token
G=$REPO/scripts/gitea-api.sh
$G GET user 2>/dev/null | grep -q '"login":"darkwing"' || { echo "STOP: login is not darkwing"; exit 1; }
echo "login darkwing ok"
for N in "$@"; do
BODY=$(python3 -B -c '
import re,sys
s=open(sys.argv[1]).read()
m=re.search(r"^## Gitea #"+sys.argv[2]+r" [^\n]*\n\n(.*?)(?=\n## |\Z)",s,re.S|re.M)
print(m.group(1).strip().replace("\n"," "),end="")' $WIN/comments.md $N) || { echo "STOP: no body for #$N"; exit 1; }
[ -n "$BODY" ] || { echo "STOP: empty body #$N"; exit 1; }
J=$(node -e 'process.stdout.write(JSON.stringify({body:process.argv[1]}))' "$BODY")
OUT=$($G POST repos/mosaicstack/stack/issues/$N/comments "$J" 2>&1) || { echo "STOP: POST #$N: $(echo "$OUT" | grep -oE 'HTTP [0-9]+')"; exit 1; }
CID=$(echo "$OUT" | grep -oE '^\{"id":[0-9]+' | grep -oE '[0-9]+$')
[ -n "$CID" ] || CID=$(echo "$OUT" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{const i=d.indexOf("{");process.stdout.write(String(JSON.parse(d.slice(i,d.lastIndexOf("}")+1)).id))})')
BACK=$($G GET repos/mosaicstack/stack/issues/comments/$CID 2>/dev/null | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(JSON.parse(d).body))')
[ "$BACK" = "$BODY" ] && echo "#$N comment $CID posted, read back equal $(date -u +%FT%TZ)" || { echo "STOP: #$N comment $CID read-back differs"; exit 1; }
done
@@ -0,0 +1,5 @@
# P6 tester reads on OLD through the socket 2026-10-23T03:42:14Z
/api/config 200 73 bytes sha256 e7ef9a40a725f356 keys ["board","tester","send","edit"] body {"board":"http://127.0.0.1:7331","tester":true,"send":false,"edit":false}
# exit 0
/api/bus/agents 200 320 bytes sha256 45b7a973397d13f9 keys ["rows","at"] agents 1
# exit 0
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), the tester thread reads (PLAN.md W6: `threads --tester` and
# `thread read --json --tester` on each existing thread). W1 takes them before W5 as the baseline, W6 again after,
# and W6 compares. Both verbs only read: thread read pages thread.messages and writes no thread_opens row
# (thread.mjs:146, broker.mjs thread.open is the only thread_opens writer). The bodies stay in $WIN/.tr/, which is
# not committed; W.txt gets one line per thread: id, message count and the sha256 of the --json output.
# usage: tr.sh W1|W6 prints the summary, exits 1 if a read fails
set -uo pipefail
WIN=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n
MOSAIC=$HOME/.npm-global/bin/mosaic
BZ=(--business mosaic-stack)
P=${1:?usage: tr.sh W1|W6}
D=$WIN/.tr/$P
[ ! -e "$D" ] || { echo "STOP $D exists"; exit 1; }
( umask 077; mkdir -p "$D" ) || exit 1
E=(env -i HOME="$HOME" PATH="$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin" LANG=C.UTF-8
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNTIME_DIR/bus}")
cd /mnt/storage/src/mosaic-stack || exit 1
"${E[@]}" $MOSAIC threads "${BZ[@]}" --json --tester > "$D/threads.json" 2> "$D/threads.err" || { echo "STOP threads --json --tester: $(head -3 "$D/threads.err")"; exit 1; }
ids=$(node -e 'for (const t of JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"))) console.log(t.id)' "$D/threads.json") || { echo "STOP threads.json unreadable"; exit 1; }
echo "$P threads --tester: $(grep -c . <<< "$ids") thread(s), threads.json sha256 $(sha256sum < "$D/threads.json" | cut -c1-16)"
rc=0
for id in $ids; do
if "${E[@]}" $MOSAIC thread read "$id" "${BZ[@]}" --json --tester > "$D/$id.json" 2> "$D/$id.err"; then
n=$(node -e 'console.log(JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).length)' "$D/$id.json")
echo "$P thread $id messages $n sha256 $(sha256sum < "$D/$id.json" | cut -c1-16)"
else
echo "$P thread $id READ FAILED: $(head -3 "$D/$id.err")"; rc=1
fi
done
exit $rc
+16
View File
@@ -0,0 +1,16 @@
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), the names every step script sources (PLAN.md "Names").
# NEW and the plan sha come from the files P0 writes ($WIN/NEW, $WIN/PLAN.sha256); a missing or malformed one stops.
REPO=/mnt/storage/src/mosaic-stack
WIN=$REPO/agents/darkwing/work/window-migrate-3n
OLD=0cbe2e9b35e51fe06dedde69c022b46e040f98c0
BASE=253346e3c3917d4c9c0f79bc87b7db6aca73180d
TOOL_BLOB=7a8c347b2be483c8c2d25a2b75b910ae615319a6
DATA=$HOME/.mosaic-dev
PLAN=$WIN/PLAN.md
MOSAIC=$HOME/.npm-global/bin/mosaic
FOUR=(scripts/stack-release.sh scripts/webui-service.sh scripts/bus-service.sh scripts/stack-copy.sh)
NEW=$(cat $WIN/NEW 2>/dev/null)
[[ "$NEW" =~ ^[0-9a-f]{40}$ ]] || { echo "STOP: $WIN/NEW is not a full sha (P0 writes it)"; exit 1; }
PLAN_SHA=$(cat $WIN/PLAN.sha256 2>/dev/null)
[[ "$PLAN_SHA" =~ ^[0-9a-f]{64}$ ]] || { echo "STOP: $WIN/PLAN.sha256 is not a sha256 (P0 writes it)"; exit 1; }
sha() { sha256sum < "$1" | cut -c1-64; }
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n) step wrapper (sage-3135): w.sh FILE TITLE CMD... runs CMD in REPO under a clean env (no ANTHROPIC_*, CLAUDE_*,
# MOSAIC_* or pi variables), appends the title, command, output, exit code and date -u stamps to window/FILE,
# prints the output, and exits with CMD's exit code.
set -uo pipefail
W=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n
f="$W/$1"; title="$2"; shift 2
E=(env -i HOME="$HOME" PATH="$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin" LANG=C.UTF-8
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNTIME_DIR/bus}")
t0=$(date -u +%FT%T.%3NZ)
{ echo; echo "## $title"; echo "# start $t0"; echo "# command: (cd \$REPO && <clean env> $*)"; } >> "$f"
out=$(cd /mnt/storage/src/mosaic-stack && "${E[@]}" "$@" 2>&1); rc=$?
printf '%s\n' "$out" >> "$f"
echo "# exit $rc, end $(date -u +%FT%T.%3NZ)" >> "$f"
printf '%s\n# exit %s\n' "$out" "$rc"
exit $rc
@@ -0,0 +1,9 @@
npm warn deprecated [email protected]: Use your platform's native DOMException instead
added 128 packages in 1s
npm warn install-scripts 3 packages had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
npm warn install-scripts [email protected] (postinstall: node install.js)
npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.
@@ -0,0 +1 @@
copied: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W0 (PLAN.md W0 row): make NEW's copy, then check it, as
# window-migrate-0cbe2e9b3/w0.sh. Added: NEW's own loader validates the live system config (row 203 and 270 keys),
# stdout discarded as config_checks does. Creates files under copies/ only; nothing live changes. Launch from /.
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
C=$HOME/.local/share/mosaic-stack/copies/$NEW
BC=$REPO/agents/sage/work/window-plan/repin-de352dfaf/business-check.mjs
fail=0
echo "start $(date -u +%FT%TZ)"
[ ! -e "$C" ] || { echo "STOP copy $C already exists"; exit 8; }
(cd "$REPO" && scripts/stack-copy.sh copy $NEW) > $WIN/w0-copy.out 2> $WIN/w0-copy.err; rc=$?
echo "stack-copy rc=$rc"; cat $WIN/w0-copy.out; cat $WIN/w0-copy.err
[ "$rc" = 0 ] || { echo "STOP stack-copy failed"; exit 1; }
d=$(diff <(git -C "$REPO" show $NEW:package-lock.json) "$C/package-lock.json") && echo "LOCK_MATCH (package-lock diff empty)" || { echo "LOCK_DIFF"; printf '%s\n' "$d" | sed -n 1,20p; fail=1; }
[ -d "$C/packages/processing-control" ] && echo "processing-control present: $(ls "$C/packages/processing-control" | tr '\n' ' ')" || { echo "processing-control MISSING"; fail=1; }
[ -d "$C/tools/unslop-hook" ] && echo "unslop-hook present: $(ls "$C/tools/unslop-hook" | tr '\n' ' ')" || { echo "unslop-hook MISSING"; fail=1; }
want=$(git -C "$REPO" ls-tree $NEW packages/library/bin/askpass | awk '{print $1}')
have=$(stat -c %a "$C/packages/library/bin/askpass" 2>&1)
echo "askpass tree mode $want, copy mode $have"
case "$want:$have" in 100755:[57][0-7][0-7]|100644:[46][0-7][0-7]) echo "ASKPASS_MODE_OK";; *) echo "ASKPASS_MODE_BAD"; fail=1;; esac
[ -x "$C/packages/library/bin/askpass" ] && echo "askpass executable bit set" || echo "askpass executable bit not set"
(cd "$C" && node -e 'import("oauth4webapi").then(()=>{console.log("oauth4webapi "+require("./node_modules/oauth4webapi/package.json").version);console.log("IMPORT_OK")},(e)=>{console.log("IMPORT_FAIL "+e.message);process.exit(1)})') || fail=1
du -sh "$C"; df -h /home | tail -1
node $BC $NEW || fail=1
v=$(cd "$C" && node scripts/mosaic-config.mjs validate 2>&1 >/dev/null); x=$?
[ $x = 0 ] && echo "NEW_LOADER_OK (scripts/mosaic-config.mjs validate on the live config, exit 0)" || { echo "NEW_LOADER_FAIL exit $x: $(head -1 <<< "$v")"; fail=1; }
echo "end $(date -u +%FT%TZ) fail=$fail"
exit $fail
@@ -0,0 +1,28 @@
start 2026-10-23T03:41:48Z
stack-copy rc=0
copied: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
npm warn deprecated [email protected]: Use your platform's native DOMException instead
added 128 packages in 1s
npm warn install-scripts 3 packages had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
npm warn install-scripts [email protected] (postinstall: node install.js)
npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.
LOCK_MATCH (package-lock diff empty)
processing-control present: package.json README.md src tests
unslop-hook present: claude-hook.js extension.ts lists.json README.md test-unslop-check.js unslop-check.js
askpass tree mode 100755, copy mode 555
ASKPASS_MODE_OK
askpass executable bit set
oauth4webapi 3.8.8
IMPORT_OK
164M /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
/dev/nvme1n1p5 458G 401G 34G 93% /home
copy 464010fbe: roles 6, projects 1, arbiters 2
HOLDS chief instance.name yes
HOLDS pm instance.name no
LOAD_OK
NEW_LOADER_OK (scripts/mosaic-config.mjs validate on the live config, exit 0)
end 2026-10-23T03:41:49Z fail=0
@@ -0,0 +1,18 @@
# W10 start 2026-10-23T03:48:46Z, TMPDIR /mnt/storage/scratch/tmp/gKMdQdA
## W10 matrix-new.txt vs matrix-old.txt (header lines differ by stamp)
(diff exit 0)
# matrix: a-g unchanged expected: FAIL=0
# listeners on 7333: 0
/api/config 200 73 bytes sha256 e7ef9a40a725f356 keys ["board","tester","send","edit"] body {"board":"http://127.0.0.1:7331","tester":true,"send":false,"edit":false} (exit 0)
/api/bus/agents 200 709 bytes sha256 bc90ef3c3ae1a464 keys ["rows","at"] agents 2 (exit 0)
# tester /api/bus/agents 200, agents 2 (P6: agents 1; the hash moves with the at field)
# tester /api/config line identical to tester-old.txt
P4 PASS: one R: spec R:0.0.0.0:7332:127.0.0.1:7332, DropInPaths empty
POST /api/seen 401 error="sign-in required"
POST /api/reply 401 error="sign-in required"
POST /api/processing 401 error="sign-in required"
tester POST /api/processing 403 error="read-only" sent=false (exit 2)
# W10 end 2026-10-23T03:48:47Z FAIL=0
W10_PASS
w10 exit 0
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W10 (PLAN.md W10 row), from window-migrate-0cbe2e9b3/w10.sh: the 7332 matrix again as matrix-new.txt against
# P6's matrix-old.txt, the two tester reads through NEW's tester-http.mjs against P6's tester-old.txt, no listener
# on 7333, P4 again, and the unsigned-write refusal baseline (mos-1195): Console POSTs through the main port with
# no session must be refused, /api/processing included. Then POST /api/processing through the tester socket must be
# refused 403 read-only (PLAN.md W10): without a session the main port answers 401 before the processing route
# runs, so the socket is the path that reaches the read-only refusal. Reads only, apart from the refused POSTs.
# Runs after w9.sh. The agents count is compared with the one P6 read into tester-old.txt, not a fixed number
# (mos-1344: the old text said "P6: agents 1" where P6 had read 2).
# usage: w10.sh
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
C=$HOME/.local/share/mosaic-stack/copies/$NEW
W=$WIN/W.txt
export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/gXXXXXX)
note() { echo "# $*" >> "$W"; echo "# $*"; }
stop() { note "STOP: $*"; exit 1; }
FAIL=0
note "W10 start $(date -u +%FT%TZ), TMPDIR $TMPDIR"
{ echo "# W10 matrix $(date -u +%FT%TZ)"; bash $WIN/p456.sh p6; } > $WIN/matrix-new.txt
{ echo; echo "## W10 matrix-new.txt vs matrix-old.txt (header lines differ by stamp)"; diff <(grep -v '^#' $WIN/matrix-old.txt) <(grep -v '^#' $WIN/matrix-new.txt); echo "(diff exit $?)"; } | tee -a "$W"
# Expected: a-g all unchanged. OLD already had the socket, so f and g were 000 at P6 too; no line is named.
for l in a b c d e f g; do
[ "$(grep "^$l " $WIN/matrix-old.txt)" = "$(grep "^$l " $WIN/matrix-new.txt)" ] || { note "matrix line $l changed"; FAIL=1; }
done
note "matrix: a-g unchanged expected: FAIL=$FAIL"
L=$(ss -ltnH 'sport = :7333' | grep -c .)
{ echo; echo "## W10 listeners on :7333"; ss -ltnH 'sport = :7333'; echo "count $L"; } >> "$W"
note "listeners on 7333: $L"
[ "$L" = 0 ] || FAIL=1
{ echo; echo "## W10 tester reads through $C/packages/cli/src/tester-http.mjs"; } >> "$W"
for p in /api/config /api/bus/agents; do
out=$(node $C/packages/cli/src/tester-http.mjs GET $p); rc=$?
# Same line shape as p456.sh tread (tester-old.txt), computed over the response text.
line=$(printf '%s' "$out" | node -e '
const r=JSON.parse(require("fs").readFileSync(0,"utf8")),s=r.text,h=require("crypto").createHash("sha256").update(s).digest("hex");
const j=r.body,shape=Array.isArray(j)?`array(${j.length})`:j?`keys ${JSON.stringify(Object.keys(j))}`:"non-json";
const list=j&&(Array.isArray(j)?j:Array.isArray(j.rows)?j.rows:null);
console.log(`${process.argv[1]} ${r.status} ${Buffer.byteLength(s)} bytes sha256 ${h.slice(0,16)} ${shape}`+
(process.argv[1]==="/api/config"?` body ${s.trim()}`:list?` agents ${list.length}`:""));' $p 2>&1)
echo "$line (exit $rc)" | tee -a "$W"
[ $rc = 0 ] || FAIL=1
done
NEWCFG=$(grep '^/api/config' "$W" | tail -1 | sed 's/ (exit .*//')
OLDCFG=$(grep '^/api/config 200 ' $WIN/tester-old.txt)
NEWAG=$(grep '^/api/bus/agents' "$W" | tail -1)
OLDAG=$(grep '^/api/bus/agents 200 ' $WIN/tester-old.txt | sed -n 's/.* agents \([0-9]*\).*/\1/p')
case "$NEWAG" in "/api/bus/agents 200 "*) note "tester /api/bus/agents 200, $(sed -n 's/.* agents \([0-9]*\).*/agents \1/p' <<< "$NEWAG") (P6: agents ${OLDAG:-unread}; the hash moves with the at field)" ;; *) note "tester /api/bus/agents is not 200"; FAIL=1 ;; esac
[ "$NEWCFG" = "$OLDCFG" ] && note "tester /api/config line identical to tester-old.txt" || { note "tester /api/config differs from tester-old.txt"; FAIL=1; }
P4=$(bash $WIN/p456.sh p4); rc=$?
{ echo; echo "## W10 P4 again"; echo "$P4"; echo "(p4 exit $rc)"; } >> "$W"
echo "$P4" | tail -1
[ $rc = 0 ] || FAIL=1
{ echo; echo "## W10 unsigned writes through the main port (no session, no proxy header; must be refused)"; } >> "$W"
for p in /api/seen /api/reply /api/processing; do
code=$(curl -s -m 10 -o $TMPDIR/body -w '%{http_code}' -X POST -H 'Host: mosaic.woltje.com' -H 'Content-Type: application/json' --data '{}' http://127.0.0.1:7332$p)
err=$(node -e 'try{const j=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));console.log("error="+JSON.stringify(j.error))}catch{console.log("non-json")}' $TMPDIR/body)
echo "POST $p $code $err" | tee -a "$W"
case $code in 401|403) ;; *) FAIL=1 ;; esac
done
out=$(echo '{}' | node $C/packages/cli/src/tester-http.mjs POST /api/processing); rc=$?
line=$(printf '%s' "$out" | node -e '
try{const r=JSON.parse(require("fs").readFileSync(0,"utf8"));console.log(`${r.status} error=${JSON.stringify(r.body?.error)} sent=${JSON.stringify(r.body?.sent)}`)}catch{console.log("unreadable")}')
echo "tester POST /api/processing $line (exit $rc)" | tee -a "$W"
case "$line" in '403 error="read-only" '*) ;; *) FAIL=1 ;; esac
rm -rf "$TMPDIR"
note "W10 end $(date -u +%FT%TZ) FAIL=$FAIL"
[ $FAIL = 0 ] && echo W10_PASS || echo W10_CHECK
@@ -0,0 +1,17 @@
# W5 go mos-1404, start 2026-10-23T03:45:57Z
# condition 3 recheck: tool blob 7a8c347b2be4, plan e664fa35, four scripts clean, pointer on OLD, P2 stubs and units PASS
stack-release: interpreter: {"path":"/usr/bin/python3.14","version":[3,14,6],"dev":66308,"ino":3968163,"sha256":"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc"}
stack-release: captured the rollback set: /home/jwoltje/.mosaic-dev/state/stack-rollback/20261023T034557Z-464010fbe4c6
stack-release: stopped both units
stack-release: stopped store schema: 3m
stack-release: backup: /home/jwoltje/.mosaic-dev/backups/stack/20261023T034557Z-464010fbe4c6
stack-release: migrated the store to 3n
stack-release: installed 464010fbe4c6925b077b9b8182a209ee2177eb9d
stack-release: gate passed on 464010fbe4c6925b077b9b8182a209ee2177eb9d
stack-release: activate ok: 464010fbe4c6925b077b9b8182a209ee2177eb9d is live for mosaic-stack
# exit 0
# W5 exit 0, W5.start 2026-10-23T03:45:57Z, end 2026-10-23T03:46:02Z
# W5: activation 20261023T034557Z-464010fbe4c6, backup /home/jwoltje/.mosaic-dev/backups/stack/20261023T034557Z-464010fbe4c6, STAMP 2026-10-23T03:45:58.274Z, SCHEMA 3m, SHA256SUMS sha256 480f5e556d02d3f079c07782af204c0a41e44fb4633064e1d7c302e473be1390 (sums check OK)
# W5 launched lines in the bus journal since W5.start: 1: launched chief: run r28dbbc5c6b4d, claude-code claude-opus-5-5
# W5 status: pointer NEW, pm true, 3n, check pass, unit flags match. W5_PASS 2026-10-23T03:46:02Z
# W5 restore plan exit 0: restore: the store must go back to 3m; needs --restore 20261023T034557Z-464010fbe4c6
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W5 (PLAN.md W5 row; window-migrate-0cbe2e9b3/w5.sh). Runs only on Mos's W5 go and Sage's
# CLEAR, after P2 passed. Rechecks the tool blob and the plan sha first; if either moved, stop (W3 runs again and goes
# to Mos). Then W3's command without --plan, --migrate included, through w.sh into W5.txt. Needs exit 0, "activate
# ok", "gate passed" (the gate's last check is pm-launch-unconfirmed, so a pass covers pm_launched), "stopped store
# schema: 3m" and "migrated the store to 3n". Records the activation id, backup path, STAMP and the backup's
# SHA256SUMS sha256, then status (schema 3n), then `rollback --restore <ACT> --plan`, which only reads, for the
# guard's post-backup count. That plan refuses post-backup-events when the count is nonzero; the script records it
# and doesn't stop. A failure stops here and goes to Mos before any rollback.
# usage: w5.sh GO (GO is Mos's W5 go label, e.g. mos-NNNN)
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
W=$WIN/W.txt
note() { echo "# $*" >> "$W"; echo "# $*"; }
stop() { note "STOP: $*"; exit 1; }
GO=${1:?usage: w5.sh GO}
note "W5 go $GO, start $(date -u +%FT%TZ)"
grep -q P2_STUBS_PASS $WIN/p2.txt 2>/dev/null || stop "p2.txt has no P2_STUBS_PASS"
grep -q P2_UNITS_PASS $WIN/p2-units.txt 2>/dev/null || stop "p2-units.txt has no P2_UNITS_PASS"
grep -q 'W5 pre-read PASS' $W || stop "W.txt has no W5 pre-read PASS (w5pre.sh)"
[ "$(git -C $REPO rev-parse HEAD:scripts/stack-release.sh)" = "$TOOL_BLOB" ] || stop "condition 3: tool blob moved; run W3 again and send it to Mos"
[ "$(sha $PLAN)" = "$PLAN_SHA" ] || stop "condition 3: plan is not P0's ${PLAN_SHA:0:8}; run W3 again and send it to Mos"
[ -z "$(git -C $REPO status --porcelain -- "${FOUR[@]}")" ] || stop "the four scripts are dirty"
grep -q "\"copy\":\"$OLD\"" $DATA/state/stack-active.json || stop "the pointer is not on OLD"
note "condition 3 recheck: tool blob ${TOOL_BLOB:0:12}, plan ${PLAN_SHA:0:8}, four scripts clean, pointer on OLD, P2 stubs and units PASS"
date -u +%FT%TZ > $WIN/W5.start
$WIN/w.sh W5.txt "W5 activate NEW --pm --migrate --tester-socket ($GO)" scripts/stack-release.sh activate $NEW mosaic-stack --pm --migrate --tester-socket; rc=$?
note "W5 exit $rc, W5.start $(cat $WIN/W5.start), end $(date -u +%FT%TZ)"
[ $rc = 0 ] || stop "W5 exit $rc; to Mos before any rollback"
grep -q "activate ok: $NEW is live for mosaic-stack" $WIN/W5.txt || stop "W5: no activate ok line"
grep -q "gate passed on $NEW" $WIN/W5.txt || stop "W5: no gate passed line"
grep -qx "stack-release: stopped store schema: 3m" $WIN/W5.txt || stop "W5: no stopped store schema: 3m line"
grep -qx "stack-release: migrated the store to 3n" $WIN/W5.txt || stop "W5: no migrated the store to 3n line"
grep -q -- 'drop --' $WIN/W5.txt && stop "W5: a drop line"
ACT=$(sed -n 's/.*"activation":"\([^"]*\)".*/\1/p' $DATA/state/stack-active.json)
BK=$(sed -n 's/^stack-release: backup: //p' $WIN/W5.txt)
[ -n "$ACT" ] && [ "$BK" = "$DATA/backups/stack/$ACT" ] || stop "W5: activation $ACT and backup $BK disagree"
[ "$(cat $BK/SCHEMA)" = 3m ] || stop "W5 backup SCHEMA is not 3m"
( cd "$BK/bus" && sha256sum -c --quiet ../SHA256SUMS ) || stop "W5 backup fails its SHA256SUMS"
echo "W5 $(date -u +%FT%TZ) activation $ACT backup $BK STAMP $(cat $BK/STAMP) SCHEMA $(cat $BK/SCHEMA) SHA256SUMS sha256 $(sha $BK/SHA256SUMS)" >> $WIN/backups.txt
note "W5: activation $ACT, backup $BK, STAMP $(cat $BK/STAMP), SCHEMA $(cat $BK/SCHEMA), SHA256SUMS sha256 $(sha $BK/SHA256SUMS) (sums check OK)"
journalctl --user -u [email protected] --since "@$(date -d "$(cat $WIN/W5.start)" +%s)" -o cat 2>/dev/null | grep '^launched ' > $WIN/W5-launched.txt
note "W5 launched lines in the bus journal since W5.start: $(wc -l < $WIN/W5-launched.txt): $(cat $WIN/W5-launched.txt)"
$WIN/w.sh W.txt "W5 status" scripts/stack-release.sh status mosaic-stack > $WIN/W5-status.txt || stop "W5 status"
sed -i '$d' $WIN/W5-status.txt
grep -q "\"copy\":\"$NEW\"" $WIN/W5-status.txt || stop "status: pointer is not NEW"
grep -q '"pm":true' $WIN/W5-status.txt && grep -q '"schema":"3n"' $WIN/W5-status.txt || stop "status: pm or schema"
grep -qx ' result: pass' $WIN/W5-status.txt && grep -qx 'unit flags: match the pointer' $WIN/W5-status.txt || stop "status: check or unit flags"
note "W5 status: pointer NEW, pm true, 3n, check pass, unit flags match. W5_PASS $(date -u +%FT%TZ)"
# The restore guard's count, read only. Any real restore goes to Mos first (PLAN.md, Stop conditions).
$WIN/w.sh W.txt "W5 rollback --restore $ACT --plan (read only)" scripts/stack-release.sh rollback mosaic-stack --restore $ACT --plan > $WIN/W5-restore-plan.txt; rrc=$?
sed -i '$d' $WIN/W5-restore-plan.txt
note "W5 restore plan exit $rrc: $(grep '^restore: \|post-backup-events' $WIN/W5-restore-plan.txt | tr '\n' ' ')"
@@ -0,0 +1,4 @@
# W5 pre-read start 2026-10-23T03:45:42Z
# W5 pre-read PASS 2026-10-23T03:45:42Z: chief r148e72c8895d standing, not working; no pm, no other launch
# W5 pre-read start 2026-10-23T03:45:57Z
# W5 pre-read PASS 2026-10-23T03:45:57Z: chief r148e72c8895d standing, not working; no pm, no other launch
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), the read right before W5 (as mos-1342 condition 1): agents --tester
# and launches list again, with W1's rule. Stops on the chief working, the pm working, or any live launch other
# than the standing chief r148e72c8895d. Starts and stops nothing.
# usage: w5pre.sh
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
W=$WIN/W.txt
note() { echo "# $*" >> "$W"; echo "# $*"; }
stop() { note "STOP: $*"; exit 1; }
CHIEF_RUN=r148e72c8895d
note "W5 pre-read start $(date -u +%FT%TZ)"
$WIN/w.sh W.txt "W5 pre agents --tester" $MOSAIC agents --business mosaic-stack --tester > $WIN/.w5pre-agents.txt || stop "W5 pre agents"
grep -E '(^|[[:space:]])chief[[:space:]].*working' $WIN/.w5pre-agents.txt && stop "W5 pre: the chief is working (mos-1340 item 2)"
grep -E '(^|[[:space:]])pm[[:space:]].*working' $WIN/.w5pre-agents.txt && stop "W5 pre: the pm is working (mos-1340 item 3)"
$WIN/w.sh W.txt "W5 pre launches list" $MOSAIC launches list > $WIN/.w5pre-launches.txt || stop "W5 pre launches list"
other=$(grep -v '^# ' $WIN/.w5pre-launches.txt | grep -v -e "^$CHIEF_RUN " | grep .)
[ -z "$other" ] || stop "W5 pre: a live launch other than the standing chief $CHIEF_RUN (mos-1340 item 1): $other"
grep -q "^$CHIEF_RUN mosaic-stack/chief " $WIN/.w5pre-launches.txt || stop "W5 pre: the standing chief launch $CHIEF_RUN is not listed"
note "W5 pre-read PASS $(date -u +%FT%TZ): chief $CHIEF_RUN standing, not working; no pm, no other launch"
@@ -0,0 +1,18 @@
# W6 start 2026-10-23T03:46:51Z
# W6 status: "copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d" "pm":true "schema":"3n"
types table=26 trigger=97 view=5 index=28
schema_version 3n
missing none
new_table_rows session_ops=0 message_cancels=0 thread_shares 0
events 2202 maxSeq 2202
COUNTS_OK
# W6 agents: chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z;
W6 threads --tester: 5 thread(s), threads.json sha256 07c154d75d929028
W6 thread d0ba7a66-aa3f-4918-9518-94132bce6b28 messages 2 sha256 985ac61ca9220f2f
W6 thread 446201de-e8b5-4df3-9356-d0d5422cc899 messages 2 sha256 7e7e9eeeaa8c1e09
W6 thread b89fcca4-6b82-48bd-9310-fb82c5ae8518 messages 2 sha256 f4e4916b9656a403
W6 thread d9bb7ff3-7a8b-4bb8-ba12-03ca094cee4e messages 2 sha256 728ff36f3e003c63
W6 thread b85a57fe-84f9-4785-a596-135af39e334a messages 2 sha256 727de0d9c809a41a
# W6 thread reads match W1: 5 threads, same ids, message counts and sha256
# W6 bus-host/sessions.json: sessionsVersion and session count: 2 1; mtime 2026-10-23T03:46:01Z
# W6_PASS 2026-10-23T03:46:56Z
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W6 (PLAN.md W6 row; window-migrate-0cbe2e9b3/w6.sh with the 3n
# counts; mos-1342 condition 3 lets it run straight after a W5 pass). Everything here only reads:
# status shows the pointer on NEW at 3n
# a python3 -B read-only count of the live store: 26 tables, 97 triggers, 5 views, 28 indexes (3m's 24/90/5/25
# plus 3m-3n.sql: session_ops and message_cancels, 7 triggers, session_ops_session and the two UNIQUE
# autoindexes), meta schema_version 3n, every 3n object by name and 3m's thread_shares set, and 0 rows in
# session_ops and message_cancels (only session.clear and session.cancel write them; nobody runs those here)
# agents --tester answers
# tr.sh W6 against W1: the same thread ids, message counts and sha256 of each thread read --json
# bus-host/sessions.json sessionsVersion 2 (it was 2 before the window; session.mjs is the same OLD to NEW);
# only the version and the count
# Since nothing changes, every check runs and the script lists each FAIL, then exits 1 if any failed. A failure
# goes to Mos; nothing restores from here.
# usage: w6.sh
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
W=$WIN/W.txt
note() { echo "# $*" >> "$W"; echo "# $*"; }
fails=0
fail() { note "W6 FAIL: $*"; fails=$((fails + 1)); }
note "W6 start $(date -u +%FT%TZ)"
grep -q 'W5_PASS' $W || { note "STOP: W.txt has no W5_PASS"; exit 1; }
$WIN/w.sh W.txt "W6 status" scripts/stack-release.sh status mosaic-stack > $WIN/W6-status.txt || fail "status exit nonzero"
grep -q "\"copy\":\"$NEW\"" $WIN/W6-status.txt || fail "status: pointer is not NEW"
grep -q '"schema":"3n"' $WIN/W6-status.txt || fail "status: schema is not 3n"
grep -qx ' result: pass' $WIN/W6-status.txt || fail "status: check is not pass"
note "W6 status: $(grep -o '"copy":"[^"]*"\|"schema":"[^"]*"\|"pm":[a-z]*' $WIN/W6-status.txt | tr '\n' ' ')"
python3 -B - "$DATA/bus/bus.sqlite" > $WIN/W6-counts.txt 2>&1 <<'PY'
import sqlite3, sys
c = sqlite3.connect("file:" + sys.argv[1] + "?mode=ro", uri=True)
types = dict(c.execute("select type, count(*) from sqlite_master group by type").fetchall())
names = {r[0] for r in c.execute("select name from sqlite_master")}
sv = c.execute("select value from meta where key='schema_version'").fetchone()
want = ["thread_shares", "thread_shares_thread", "thread_shares_no_update", "thread_shares_no_delete", "thread_shares_no_replace",
"session_ops", "message_cancels", "session_ops_session",
"session_ops_no_update", "session_ops_no_delete", "session_ops_no_replace", "session_ops_one_open",
"message_cancels_no_update", "message_cancels_no_delete", "message_cancels_no_replace"]
def rc(t):
return c.execute(f"select count(*) from {t}").fetchone()[0] if t in names else None
rows = {t: rc(t) for t in ("session_ops", "message_cancels")}
ev = c.execute("select count(*), max(seq) from events").fetchone()
print("types", " ".join(f"{k}={types.get(k)}" for k in ("table", "trigger", "view", "index")))
print("schema_version", sv[0] if sv else None)
print("missing", " ".join(n for n in want if n not in names) or "none")
print("new_table_rows", " ".join(f"{k}={v}" for k, v in rows.items()), "thread_shares", rc("thread_shares"))
print("events", ev[0], "maxSeq", ev[1])
ok = (types.get("table"), types.get("trigger"), types.get("view"), types.get("index")) == (26, 97, 5, 28) \
and sv and sv[0] == "3n" and all(n in names for n in want) and all(v == 0 for v in rows.values())
print("COUNTS_OK" if ok else "COUNTS_BAD")
PY
crc=$?
{ echo; echo "## W6 live store count (python3 -B, mode=ro)"; cat $WIN/W6-counts.txt; echo "# exit $crc"; } >> $W
cat $WIN/W6-counts.txt
grep -qx COUNTS_OK $WIN/W6-counts.txt || fail "store count: $(grep -v '^COUNTS' $WIN/W6-counts.txt | tr '\n' ';')"
$WIN/w.sh W.txt "W6 agents --tester" $MOSAIC agents --business mosaic-stack --tester > $WIN/.w6-agents.txt || fail "agents --tester exit nonzero"
note "W6 agents: $(grep -v '^# ' $WIN/.w6-agents.txt | tr '\n' ';')"
bash $WIN/tr.sh W6 > $WIN/.w6-tr.txt 2>&1; trc=$?
{ echo; echo "## W6 tester thread reads (tr.sh W6)"; cat $WIN/.w6-tr.txt; echo "# exit $trc"; } >> $W; cat $WIN/.w6-tr.txt
[ $trc = 0 ] || fail "tr.sh W6 exit $trc"
a=$(sed -n 's/^W1 thread //p' $WIN/.w1-tr.txt | sort); b=$(sed -n 's/^W6 thread //p' $WIN/.w6-tr.txt | sort)
if [ -n "$a" ] && [ "$a" = "$b" ]; then
note "W6 thread reads match W1: $(grep -c . <<< "$a") threads, same ids, message counts and sha256"
else
fail "thread reads differ from W1: $(diff <(echo "$a") <(echo "$b") | grep '^[<>]' | tr '\n' ';')"
fi
sv=$(python3 -B -c '
import json, sys
d = json.load(open(sys.argv[1]))
print(d.get("sessionsVersion"), len(d.get("sessions") or []))' $DATA/bus-host/sessions.json 2>&1)
note "W6 bus-host/sessions.json: sessionsVersion and session count: $sv; mtime $(date -u -r $DATA/bus-host/sessions.json +%FT%TZ)"
case "$sv" in "2 "*) ;; *) fail "sessions.json is not version 2: $sv" ;; esac
if [ $fails = 0 ]; then note "W6_PASS $(date -u +%FT%TZ)"; else note "W6 end $(date -u +%FT%TZ): $fails FAIL(s), to Mos"; exit 1; fi
@@ -0,0 +1,63 @@
# W9 after W5, W5.start 2026-10-23T03:45:57Z, start 2026-10-23T03:47:16Z
# plan e664fa35, tool blob 7a8c347b2be4, console-check.sh d068fe02, wrapper on NEW: checked
# Console: unit active, pid 3811007, started 2026-10-23T03:45:59Z, cwd /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
# bus: unit active, pid 3811005, cwd /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"464010fbe4c6925b077b9b8182a209ee2177eb9d","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3n","activation":"20261023T034557Z-464010fbe4c6","at":"2026-10-23T03:46:02Z"}
log (last five lines):
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
{"at":"2026-10-23T03:45:57Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":null,"activation":"20261023T034557Z-464010fbe4c6","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-23T03:46:02Z","op":"activate","from":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","to":"464010fbe4c6925b077b9b8182a209ee2177eb9d","backup":"20261023T034557Z-464010fbe4c6","activation":"20261023T034557Z-464010fbe4c6","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
Console: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/464010fbe4c6925b077b9b8182a209ee2177eb9d
result: pass
bus status:
host: mosaic-stack, pid 3811005, running, since 2026-10-23T03:45:59.548Z, notifier off
socket: present
writer.lock: pid 3811049
boot: organization file loaded at 2026-10-23T03:45:59.547Z
unit flags: match the pointer
drift: 0 commit(s) on refactor since the pointer's copy
# exit 0
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=ae0b17c9586e51da7bc1b7e548c6acf913a55665 files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=ae0b17c9586e51da7bc1b7e548c6acf913a55665 length=99 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
# exit 0
# journal since W5: proxy proof off 1, Sign-in 0
# console/ drwx------ jwoltje directory; tester.sock srw------- jwoltje socket
# peercred-starts lines since W5 naming NEW: 1
# bus journal library: 0
r28dbbc5c6b4d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-23T03:46:01.221Z running
# exit 0
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r28dbbc5c6b4d",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 3811352,
"startTime": "82835140",
"runnerPid": 3811353,
"runnerStartTime": "82835140",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r28dbbc5c6b4d",
"startedAt": "2026-10-23T03:46:01.221Z",
"live": true
}
]
# exit 0
# one running chief from this start: run r28dbbc5c6b4d
# pm: no pm launch is listed, as expected (none live since the last window; mos-1344)
chief r28dbbc5c6b4d claude-code since 2026-10-23T03:46:01.235Z
# exit 0
# W9 end 2026-10-23T03:47:17Z W9_CHECKS_PASS
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W9 (PLAN.md W9 row). Taken from window-migrate-0cbe2e9b3/w9.sh with
# the mos-1344 carry: the tester pm read is gone, since no pm is live (a pm launch on NEW is W9b, only on Mos's
# ruling). The checks are the same: mos-1338 item 4 (the Console's unit state, MainPID, start time and cwd; a miss
# stops and goes to Mos, who restarts it, never me), status at 3n, console-check, the webui journal counts, the
# tester socket modes, peercred-starts, the bus library: lines, one running chief from this start, no pm launch,
# and the chief not working. Every call goes through w.sh into W.txt; every bus call carries --tester. A failed
# check stops the script.
# usage: w9.sh
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
W5_ISO=$(cat $WIN/W5.start) || exit 1
W5_STAMP="$(date -u -d "$W5_ISO" '+%F %T') UTC"
W5_S=$(date -d "$W5_ISO" +%s)
CC=$REPO/agents/sage/work/window-plan/repin-de352dfaf/console-check.sh
CC_SHA=d068fe02da897b700110a162d13d9671fe5994f053c45aff99113fade737094c
COPY=$HOME/.local/share/mosaic-stack/copies/$NEW
BZ=(--business mosaic-stack)
W=$WIN/W.txt
note() { echo "# $*" >> "$W"; echo "# $*"; }
stop() { note "STOP: $*"; exit 1; }
step() { local f="$1"; shift; $WIN/w.sh W.txt "$@" > "$WIN/$f"; local rc=$?; cat "$WIN/$f"; return $rc; }
note "W9 after W5, W5.start $W5_ISO, start $(date -u +%FT%TZ)"
[ "$(sha $PLAN)" = "$PLAN_SHA" ] || stop "the plan file is not P0's ${PLAN_SHA:0:8}"
git -C $REPO rev-parse HEAD:scripts/stack-release.sh | grep -q "^$TOOL_BLOB" || stop "tool blob moved"
[ "$(sha $CC)" = "$CC_SHA" ] || stop "console-check.sh is not d068fe02"
grep -q "/copies/$NEW/scripts/mosaic" "$MOSAIC" || stop "the wrapper is not pinned to NEW"
note "plan ${PLAN_SHA:0:8}, tool blob ${TOOL_BLOB:0:12}, console-check.sh d068fe02, wrapper on NEW: checked"
# mos-1338 item 4: the Console unit, pid, start time and cwd. A miss stops here and goes to Mos.
U_ACTIVE=$(systemctl --user is-active mosaic-webui.service)
CONSOLE_PID=$(systemctl --user show -p MainPID --value mosaic-webui.service)
U_START=$(systemctl --user show -p ExecMainStartTimestamp --value mosaic-webui.service)
U_START_S=$(date -d "$U_START" +%s 2>/dev/null || echo 0)
P_START=$(ps -o lstart= -p "$CONSOLE_PID" 2>/dev/null)
CWD=$(readlink /proc/$CONSOLE_PID/cwd 2>/dev/null)
{ echo; echo "## W9 Console unit (mos-1338 item 4)"; echo "is-active $U_ACTIVE, MainPID $CONSOLE_PID"
echo "ExecMainStartTimestamp $U_START ($(date -u -d @$U_START_S +%FT%TZ)), ps lstart $P_START"; echo "cwd -> $CWD"; } >> "$W"
note "Console: unit $U_ACTIVE, pid $CONSOLE_PID, started $(date -u -d @$U_START_S +%FT%TZ), cwd $CWD"
[ "$U_ACTIVE" = active ] || stop "mosaic-webui is $U_ACTIVE, not active (mos-1338 item 4; to Mos, who restarts it)"
[ -n "$CONSOLE_PID" ] && [ "$CONSOLE_PID" != 0 ] || stop "no Console MainPID (to Mos)"
[ "$U_START_S" -ge "$W5_S" ] || stop "the Console started $(date -u -d @$U_START_S +%FT%TZ), before W5.start $W5_ISO (mos-1338 item 4; to Mos)"
[ "$CWD" = "$COPY" ] || stop "Console cwd is not the NEW copy (mos-1338 item 4; to Mos)"
BUS_PID=$(systemctl --user show -p MainPID --value [email protected])
BUS_CWD=$(readlink /proc/$BUS_PID/cwd 2>/dev/null)
note "bus: unit $(systemctl --user is-active [email protected]), pid $BUS_PID, cwd $BUS_CWD"
[ "$BUS_CWD" = "$COPY" ] || stop "bus cwd is not the NEW copy"
# The last window's W9 checks.
step .w9-status.txt "W9 status" $REPO/scripts/stack-release.sh status mosaic-stack || stop "W9 status exit"
grep -q "\"copy\":\"$NEW\"" $WIN/.w9-status.txt && grep -q '"pm":true' $WIN/.w9-status.txt && grep -q '"schema":"3n"' $WIN/.w9-status.txt || stop "W9 status: pointer not on NEW, pm true, 3n"
grep -q 'unit flags: match the pointer' $WIN/.w9-status.txt || stop "W9 status: unit flags"
grep -q 'result: pass' $WIN/.w9-status.txt || stop "W9 status: check result"
step .w9-cc.txt "W9 console-check NEW ($CC, d068fe02)" bash $CC $NEW || stop "console-check exit"
grep -qx CONSOLE_PASS $WIN/.w9-cc.txt || stop "console-check not CONSOLE_PASS"
J=$(journalctl --user -u mosaic-webui.service --since "$W5_STAMP" --no-pager)
N=$(printf '%s\n' "$J" | grep -c 'Proxy: proxy proof off'); S=$(printf '%s\n' "$J" | grep -c 'Sign-in:')
{ echo; echo "## W9 webui journal since $W5_STAMP: 'Proxy: proxy proof off' and 'Sign-in:' counts"; echo "proxy proof off $N, Sign-in $S"; } >> "$W"
note "journal since W5: proxy proof off $N, Sign-in $S"
[ "$N" = 1 ] || stop "proxy proof off count is $N, not 1 (one Console start since W5)"
[ "$S" = 0 ] || stop "Sign-in count is $S, not 0"
SOCK_LINE=$(stat -c '%A %U %F' $DATA/console/tester.sock); DIR_LINE=$(stat -c '%A %U %F' $DATA/console)
{ echo; echo "## W9 tester socket"; echo "console/ $DIR_LINE"; echo "tester.sock $SOCK_LINE"; } >> "$W"
note "console/ $DIR_LINE; tester.sock $SOCK_LINE"
[ "$SOCK_LINE" = "srw------- $USER socket" ] || stop "tester.sock is not a 0600 socket"
[ "$DIR_LINE" = "drwx------ $USER directory" ] || stop "console/ is not 0700"
R=$(python3 -B -c '
import json,sys
n=0
for l in open(sys.argv[1]):
d=json.loads(l)
if d["at"]>=sys.argv[2] and sys.argv[3] in d["copy"]: n+=1
print(n)' $DATA/bus/peercred-starts.jsonl $W5_ISO $NEW) || stop "peercred-starts.jsonl unreadable"
{ echo; echo "## W9 peercred-starts.jsonl lines since W5 naming NEW"; echo "$R (file lines $(wc -l < $DATA/bus/peercred-starts.jsonl))"; } >> "$W"
note "peercred-starts lines since W5 naming NEW: $R"
[ "$R" = 1 ] || stop "peercred-starts has $R lines for this start, not 1"
BJ=$(journalctl --user -u [email protected] --since "$W5_STAMP" --no-pager -o cat)
L=$(printf '%s\n' "$BJ" | grep -c 'library:')
{ echo; echo "## W9 bus journal 'library:' lines since W5"; echo "library: $L"; } >> "$W"
note "bus journal library: $L"
[ "$L" = 0 ] || stop "bus journal has $L library: lines"
step .w9-launches.txt "W9 launches list" $MOSAIC launches list || stop "launches list"
step .w9-launches.json "W9 launches list --json" $MOSAIC launches list --json || stop "launches list --json"
sed -i '$d' $WIN/.w9-launches.json
CHIEF=$(node -e '
const l=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")),since=Number(process.argv[2]);
const c=l.filter(x=>x.business==="mosaic-stack"&&x.instance==="chief"&&x.live===true);
if(c.length!==1||!(Date.parse(c[0].startedAt)>=since)){console.error(`running chiefs ${c.length}`);process.exit(1)}
process.stdout.write(c[0].run)' $WIN/.w9-launches.json "${W5_S}000") || stop "launches: not one running chief from this start"
note "one running chief from this start: run $CHIEF"
grep -q 'mosaic-stack/pm ' $WIN/.w9-launches.txt && stop "pm: a pm launch is listed; none was live before the window and nothing here starts one (to Mos)"
note "pm: no pm launch is listed, as expected (none live since the last window; mos-1344)"
step .w9-agents.txt "W9 agents --tester" $MOSAIC agents "${BZ[@]}" --tester || stop "agents"
grep -E '(^|[[:space:]])chief[[:space:]].*working' $WIN/.w9-agents.txt && stop "the chief is working"
note "W9 end $(date -u +%FT%TZ) W9_CHECKS_PASS"
@@ -0,0 +1,25 @@
# W9b start 2026-10-23T03:47:25Z
# before the relay: chief r28dbbc5c6b4d live, no pm
# backup /home/jwoltje/.mosaic-dev/backups/b0-20261023T034725Z, SHA256SUMS 0946b66ccde18f5be8d49ed9f15cccf09f73804abdf31b3ae9c108c6c9ee9272
sent c66b0184 to chief
chief (RESULT, re c66b0184) 11e140fa:
I passed your request to the pm in your own words (message 3479e027-0099-4ce8-a106-0b1115e00dd2), with your note that it's a test and authorizes nothing. You asked for the PRD version and the number of ids in packages/tasks/requirements.json, read through its requirements read.
No pm session was running when I sent it, so I launched one (run ra38826f3c6e2, claude-code, claude-opus-5-5). Nothing was refused. When the pm answers, I'll pass on what it says.
# exit 0
r28dbbc5c6b4d mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-23T03:46:01.221Z running
ra38826f3c6e2 mosaic-stack/pm claude-code claude-opus-5-5 by chief since 2026-10-23T03:47:35.835Z running
# exit 0
# pm run ra38826f3c6e2 (claude-code), launchedBy chief, startedAt 2026-10-23T03:47:35.835Z, pid 3831373, runner pid 3831374: left live by W9b
# pm launched by chief after the relay: run ra38826f3c6e2 (claude-code), pid 3831373, runner pid 3831374
# pid 3831373: argv entries naming NEW's copy 37, cwd names NEW's copy no
# pid 3831374: argv entries naming NEW's copy 1, cwd names NEW's copy no
# the pm runs from NEW's copy
sent a2d9eeb9 to pm
pm (RESULT, re a2d9eeb9) 4500c1dd:
Version 1.15, 85 ids (checked 10 seconds ago with list_requirements and against packages/tasks/requirements.json at 464010f).
# exit 0
# pm read: 1.15 and 85 found in the reply
# W9b end 2026-10-23T03:47:51Z W9B_PASS
W9B_PASS
w9b exit 0
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W9b: the pm launch proof on NEW (PLAN.md W9b section). Mos ruled
# it in (mos-1392, through sage-3151); it is the one step that writes chief and pm turns, messages and a pm launch.
# Taken from window-repin-84f53540d/w12c.sh step 1, without row 66's turns, under Mos's five conditions:
# 4. Hold first: sage-busy-now.sh must print CLEAR (574 not in a run phase), or W9B_HOLD with no bus write.
# 1. Backup first: b0.sh 3n, the online copy of the held 3n store; its B0_PASS (integrity_check ok and
# sha256sum -c) comes before the relay.
# One `talk chief --tester` relay that gives the pm real work, tagged [mos test] (Q134 C), with no staffing ask
# and no authorization text.
# launches list --json within 120 s: exactly one live pm, launchedBy chief, startedAt at or after the relay.
# 2. Its outer pid's or runnerPid's /proc cmdline or cwd must name NEW's copy. Only the match is written down
# (a count and a yes or no), never argv, never the environment.
# One `talk pm --tester` read, expecting 1.15 and 85 ids.
# 3. Once the pm is seen, its run id, pid and runner pid are written as "left live by W9b" for STATE.md and
# the P19 body ($WIN/W9B-PM.txt), whatever happens after.
# 5. The first failure stops with exit 1, for Mos and Sage. A pm that launched and failed its read stays live.
# Every bus call carries --tester.
# usage: w9b.sh
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
C=$HOME/.local/share/mosaic-stack/copies/$NEW
BZ=(--business mosaic-stack)
W=$WIN/W.txt
note() { echo "# $*" >> "$W"; echo "# $*"; }
stop() { note "STOP: $*"; exit 1; }
step() { local f="$1"; shift; $WIN/w.sh W.txt "$@" > "$WIN/$f"; local rc=$?; cat "$WIN/$f"; return $rc; }
if ! bash /mnt/storage/scratch/sage-busy-now.sh 2>/dev/null | head -3 | grep -q '^CLEAR'; then
note "W9b hold $(date -u +%FT%TZ): the busy check isn't CLEAR, so a run phase may be on; nothing sent (condition 4) W9B_HOLD"
exit 2
fi
note "W9b start $(date -u +%FT%TZ)"
grep -q 'W9_CHECKS_PASS' $W || stop "W.txt has no W9_CHECKS_PASS"
grep -q "/copies/$NEW/scripts/mosaic" "$MOSAIC" || stop "the wrapper is not pinned to NEW"
CHIEF=$($MOSAIC launches list --json 2>/dev/null | node -e '
const l=JSON.parse(require("fs").readFileSync(0,"utf8"));
const c=l.filter(x=>x.business==="mosaic-stack"&&x.instance==="chief"&&x.live===true);
const p=l.filter(x=>x.business==="mosaic-stack"&&x.instance==="pm"&&x.live===true);
if(c.length!==1||p.length!==0)process.exit(1); process.stdout.write(c[0].run)') || stop "not one live chief and no live pm before the relay"
note "before the relay: chief $CHIEF live, no pm"
bash $WIN/b0.sh 3n > $WIN/.w9b-b0.txt 2>&1; brc=$?
{ echo; echo "## W9b backup first (b0.sh 3n)"; cat $WIN/.w9b-b0.txt; echo "# exit $brc"; } >> "$W"
[ $brc = 0 ] && grep -q B0_PASS $WIN/.w9b-b0.txt || stop "b0.sh 3n: $(tail -1 $WIN/.w9b-b0.txt)"
note "backup $(sed -n 's/^B0 path //p' $WIN/.w9b-b0.txt), SHA256SUMS $(sed -n 's/^B0 SHA256SUMS sha256 //p' $WIN/.w9b-b0.txt)"
MARK_MS=$(date +%s%3N)
step .w9b-talk-chief.txt "W9b talk chief --wait 300 --tester (relay to pm)" $MOSAIC talk chief "[mos test] Please relay this to the pm: [mos test] read packages/tasks/requirements.json through your requirements read and reply with the PRD version and the number of ids." --wait 300 "${BZ[@]}" --tester || stop "talk chief relay failed or timed out (to Mos and Sage)"
# Poll until any live pm shows, then judge it once (condition 5 names each failure).
PMJ=""
for i in $(seq 1 24); do
PMJ=$($MOSAIC launches list --json 2>/dev/null | node -e '
const l=JSON.parse(require("fs").readFileSync(0,"utf8"));
const p=l.filter(x=>x.business==="mosaic-stack"&&x.instance==="pm"&&x.live===true);
if(p.length)process.stdout.write(JSON.stringify(p.map(x=>({run:x.run,harness:x.harness,launchedBy:x.launchedBy,startedAt:x.startedAt,pid:x.pid,runnerPid:x.runnerPid}))))')
[ -n "$PMJ" ] && break
sleep 5
done
step .w9b-launches.txt "W9b launches list" $MOSAIC launches list || stop "launches list"
[ -n "$PMJ" ] || stop "live pm count 0 within 120 s of the relay; no more chief turns for it (to Mos and Sage) W9B_OPEN"
N=$(node -e 'process.stdout.write(String(JSON.parse(process.argv[1]).length))' "$PMJ")
# Condition 3: every live pm seen is written down as left live, before any other check.
node -e '
for (const x of JSON.parse(process.argv[1])) console.log(`pm run ${x.run} (${x.harness}), launchedBy ${x.launchedBy}, startedAt ${x.startedAt}, pid ${x.pid}, runner pid ${x.runnerPid}: left live by W9b`)' "$PMJ" > $WIN/W9B-PM.txt
while read -r l; do note "$l"; done < $WIN/W9B-PM.txt
[ "$N" = 1 ] || stop "live pm count $N, not 1 (to Mos and Sage)"
read -r PMRUN PMH BY SA OPID RPID < <(node -e '
const x=JSON.parse(process.argv[1])[0]; process.stdout.write(`${x.run} ${x.harness} ${x.launchedBy} ${x.startedAt} ${x.pid} ${x.runnerPid}`)' "$PMJ")
[ "$BY" = chief ] || stop "pm run $PMRUN launchedBy $BY, not chief (to Mos and Sage)"
SA_MS=$(node -e 'const t=Date.parse(process.argv[1]); process.stdout.write(Number.isFinite(t)?String(t):"NaN")' "$SA")
[ "$SA_MS" != NaN ] && [ "$SA_MS" -ge "$MARK_MS" ] || stop "pm run $PMRUN startedAt $SA is before the relay ($(date -u -d @$((MARK_MS/1000)) +%FT%TZ)) (to Mos and Sage)"
note "pm launched by chief after the relay: run $PMRUN ($PMH), pid $OPID, runner pid $RPID"
hit=0
for P in $OPID $RPID; do
a=$(tr '\0' '\n' < /proc/$P/cmdline 2>/dev/null | grep -c "/copies/$NEW\(/\|\$\)"); c=$(readlink /proc/$P/cwd 2>/dev/null)
case "$c" in */copies/$NEW|*/copies/$NEW/*) cw=yes ;; *) cw=no ;; esac
note "pid $P: argv entries naming NEW's copy ${a:-0}, cwd names NEW's copy $cw"
{ [ "${a:-0}" -gt 0 ] || [ $cw = yes ]; } && hit=1
done
[ $hit = 1 ] || stop "neither pm process names NEW's copy path (to Mos and Sage)"
note "the pm runs from NEW's copy"
step .w9b-talk-pm.txt "W9b pm read talk pm --wait 300 --tester" $MOSAIC talk pm "[mos test] read packages/tasks/requirements.json through your requirements read and reply with one line: the version and the number of ids" --wait 300 "${BZ[@]}" --tester || stop "talk pm failed or timed out; the pm stays live (to Mos and Sage)"
grep -q '1\.15' $WIN/.w9b-talk-pm.txt && grep -qw '85' $WIN/.w9b-talk-pm.txt || stop "pm read: the reply does not show 1.15 and 85, see .w9b-talk-pm.txt; the pm stays live (to Mos and Sage)"
note "pm read: 1.15 and 85 found in the reply"
note "W9b end $(date -u +%FT%TZ) W9B_PASS"
echo W9B_PASS
@@ -0,0 +1,50 @@
# W0b-W4 start 2026-10-23T03:44:07Z
# plan e664fa35, NEW 464010fbe4c6925b077b9b8182a209ee2177eb9d, tool blob 7a8c347b2be4, P5 four scripts at NEW and clean, business-check.mjs eece4d46: checked 2026-10-23T03:44:07Z
# W0 copy present, w0.txt LOAD_OK
# W0b: /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261023T034407Z, sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93, recorded in backups.txt
# W1 launches: chief r148e72c8895d standing (OLD's activation), no other launch
# W1 start stamp 2026-10-23T03:44:08Z
# SNAP sha256 639fd28a checked before S1
# S1.json sha256 7e4d46f9bc094ae10f1c76dfaee54407068bf99f52f8e465f36bc0bbcd961e68
W1 threads --tester: 5 thread(s), threads.json sha256 07c154d75d929028
W1 thread d0ba7a66-aa3f-4918-9518-94132bce6b28 messages 2 sha256 985ac61ca9220f2f
W1 thread 446201de-e8b5-4df3-9356-d0d5422cc899 messages 2 sha256 7e7e9eeeaa8c1e09
W1 thread b89fcca4-6b82-48bd-9310-fb82c5ae8518 messages 2 sha256 f4e4916b9656a403
W1 thread d9bb7ff3-7a8b-4bb8-ba12-03ca094cee4e messages 2 sha256 728ff36f3e003c63
W1 thread b85a57fe-84f9-4785-a596-135af39e334a messages 2 sha256 727de0d9c809a41a
# W1 D3: /home/jwoltje/.mosaic-dev/backups/d3-20261023T034412Z, STAMP 2026-10-23T03:44:12.165Z, SHA256SUMS sha256 eda5be89461d50c49cc3c2f32a03f49d3cd6d3848371e9de2eed183cec7cb74d, members 91652 tar exit 0 sessions.json sessionsVersion 2
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--tester-socket"],"schema":"3m","activation":"20261022T201621Z-0cbe2e9b35e5","at":"2026-10-22T20:16:24Z"}
log (last five lines):
{"at":"2026-10-22T04:03:00Z","op":"activate","from":"7247d80c7af7ffa70fc61557406bf491cdf3bfc7","to":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","backup":"20261022T040256Z-86f95bb27f6c","activation":"20261022T040256Z-86f95bb27f6c","result":"ok"}
{"at":"2026-10-22T15:05:55Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":null,"activation":"20261022T150555Z-84f53540dd0d","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T15:06:00Z","op":"activate","from":"86f95bb27f6cb237bf9fa9ba53320bace6c4de0b","to":"84f53540dd0d21ef179ca29697e172b0246e78d1","backup":"20261022T150555Z-84f53540dd0d","activation":"20261022T150555Z-84f53540dd0d","result":"ok"}
{"at":"2026-10-22T20:16:21Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":null,"activation":"20261022T201621Z-0cbe2e9b35e5","result":"started","interpreter":"{\"path\":\"/usr/bin/python3.14\",\"version\":[3,14,6],\"dev\":66308,\"ino\":3968163,\"sha256\":\"8735f172622a7f8d27e293fd0fd36fc55c4cb9b663b6630d33b57870e36c24cc\"}"}
{"at":"2026-10-22T20:16:24Z","op":"activate","from":"84f53540dd0d21ef179ca29697e172b0246e78d1","to":"0cbe2e9b35e51fe06dedde69c022b46e040f98c0","backup":"20261022T201621Z-0cbe2e9b35e5","activation":"20261022T201621Z-0cbe2e9b35e5","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
Console: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
result: pass
bus status:
host: mosaic-stack, pid 1791120, running, since 2026-10-22T20:16:23.031Z, notifier off
socket: present
writer.lock: pid 1791164
boot: organization file loaded at 2026-10-22T20:16:23.030Z
unit flags: match the pointer
drift: 94 commit(s) on refactor since the pointer's copy
# exit 0
# W3-plan.txt sha256 229888d8d03a3be1a46ad505ed50f85c435aec580a161cbb2e7bceba03ae866b
# W3 commit list lines left out of the flag checks: 94
# W3 plan checks: OLD to NEW, 3m to 3n in one step, 94 commits, needs --migrate (given) and the decision 96 line, backup line, Console line OLD's flags exactly, proof off, send off, no drop/sso/send/proof, bus --pm: PASS
DropInPaths=
ExecStart={ path=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/scripts/mosaic ; argv[]=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/scripts/mosaic bus start mosaic-stack --pm ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
# exit 0
DropInPaths=
ExecStart={ path=/usr/bin/node ; argv[]=/usr/bin/node /home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0/packages/webui/src/cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
WorkingDirectory=/home/jwoltje/.local/share/mosaic-stack/copies/0cbe2e9b35e51fe06dedde69c022b46e040f98c0
# exit 0
# W4 checks: DropInPaths empty on both, both ExecStart name the OLD copy, bus has --pm, no quote or backslash in either ExecStart
# W4 bus Environment: 2 assignments; quote or backslash: none (values not recorded)
# W0b-W4 end 2026-10-23T03:44:33Z
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# Migrate window 0cbe2e9b3 to NEW (3m to 3n), W0b-W4 (PLAN.md, sha256 in PLAN.sha256 from P0; sage-3135, mos-1385).
# W0 ran in prep (w0.txt). Adapted from window-migrate-0cbe2e9b3/wsteps.sh: W1's launch rule now expects the one
# standing chief from OLD's activation and no pm (any other launch goes to Mos), and W3 checks 3m to 3n in one step
# with the commit count read from git. W1-W4 start nothing, but S1, W2 and W3 read the store, so the script runs
# only on Sage's CLEAR. W3 and W4 go to Mos for the W5 go.
# Nothing is stopped. Each step goes through w.sh into W.txt; a failed check stops the script.
# usage: wsteps.sh
set -uo pipefail
. /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-migrate-3n/vars.sh
BC=$REPO/agents/sage/work/window-plan/repin-de352dfaf/business-check.mjs
BC_SHA=eece4d4671e1ce15ba81e1654c641925ac4988b343687b062897392285a8a1a7
BIZ=$HOME/.config/mosaic-dev/businesses/mosaic-stack.json
BIZ_SHA=1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93
SNAP=$REPO/agents/darkwing/work/window-repin-84f53540d/snapshot.mjs
SNAP_SHA=639fd28a2674aaa03ce824e3394b4052f21cdef7d6fa49bae61a9927ed341e67
W=$WIN/W.txt
NCOMMITS=$(git -C $REPO rev-list --count $OLD..$NEW)
CHIEF_RUN=r148e72c8895d
note() { echo "# $*" >> "$W"; echo "# $*"; }
stop() { note "STOP: $*"; exit 1; }
note "W0b-W4 start $(date -u +%FT%TZ)"
[ "$(sha $PLAN)" = "$PLAN_SHA" ] || stop "the plan file is not P0's ${PLAN_SHA:0:8}"
[ "$(git -C $REPO rev-parse HEAD:scripts/stack-release.sh)" = "$TOOL_BLOB" ] || stop "tool blob moved"
git -C $REPO diff --quiet $NEW HEAD -- "${FOUR[@]}" || stop "P5: the four scripts differ from NEW"
[ -z "$(git -C $REPO status --porcelain -- "${FOUR[@]}")" ] || stop "P5: the four scripts are dirty"
[ "$(sha $BC)" = "$BC_SHA" ] || stop "business-check.mjs sha"
note "plan ${PLAN_SHA:0:8}, NEW $NEW, tool blob ${TOOL_BLOB:0:12}, P5 four scripts at NEW and clean, business-check.mjs eece4d46: checked $(date -u +%FT%TZ)"
# W0 ran in prep (w0.txt): the copy must still be there with LOAD_OK recorded.
[ -f "$HOME/.local/share/mosaic-stack/copies/$NEW/scripts/mosaic" ] || stop "W0 copy missing"
grep -q LOAD_OK $WIN/w0.txt || stop "w0.txt has no LOAD_OK"
note "W0 copy present, w0.txt LOAD_OK"
# W0b
[ "$(sha $BIZ)" = "$BIZ_SHA" ] || stop "business file sha256 is not P1's 1e49bdf0"
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
COPY=$BIZ.pre-window-$STAMP
[ ! -e "$COPY" ] || stop "$COPY exists"
cp -p "$BIZ" "$COPY" || stop "W0b cp"
[ "$(sha "$COPY")" = "$BIZ_SHA" ] || stop "W0b copy sha256"
echo "W0b $(date -u +%FT%TZ) business file copy $COPY sha256 $(sha "$COPY") (source $BIZ sha256 $(sha "$BIZ"))" >> $WIN/backups.txt
note "W0b: $COPY, sha256 $(sha "$COPY"), recorded in backups.txt"
# W1
$WIN/w.sh W.txt "W1 agents --tester" $MOSAIC agents --business mosaic-stack --tester > $WIN/.w1-agents.txt || stop "W1 agents"
grep -E '(^|[[:space:]])chief[[:space:]].*working' $WIN/.w1-agents.txt && stop "W1: the chief is working (mos-1340 item 2)"
grep -E '(^|[[:space:]])pm[[:space:]].*working' $WIN/.w1-agents.txt && stop "W1: the pm is working (mos-1340 item 3)"
$WIN/w.sh W.txt "W1 launches list" $MOSAIC launches list > $WIN/.w1-launches.txt || stop "W1 launches list"
# mos-1340's rule with this window's runs: r148e72c8895d is the standing chief launch from OLD's activation (the
# last window's W5-launched.txt and .w9-launches.json). No pm was live at the last window's end. Any other live
# launch, a pm included, goes to Mos before S1.
other=$(grep -v '^# ' $WIN/.w1-launches.txt | grep -v -e "^$CHIEF_RUN " | grep .)
[ -z "$other" ] || stop "W1: a live launch other than the standing chief $CHIEF_RUN (mos-1340 item 1): $other"
grep -q "^$CHIEF_RUN mosaic-stack/chief " $WIN/.w1-launches.txt || stop "W1: the standing chief launch $CHIEF_RUN is not listed"
note "W1 launches: chief $CHIEF_RUN standing (OLD's activation), no other launch"
note "W1 start stamp $(date -u +%FT%TZ)"
[ "$(sha $SNAP)" = "$SNAP_SHA" ] || stop "SNAP sha before S1"
note "SNAP sha256 639fd28a checked before S1"
$WIN/w.sh W.txt "S1 snapshot --held" node $SNAP --store "$DATA/bus/bus.sqlite" --held > $WIN/S1.json || stop "S1"
sed -i '$d' $WIN/S1.json # w.sh prints "# exit N" last; S1.json keeps the snapshot only
note "S1.json sha256 $(sha $WIN/S1.json)"
# W6's baseline: the tester thread reads before W5 (tr.sh; bodies in $WIN/.tr/W1, not committed).
bash $WIN/tr.sh W1 > $WIN/.w1-tr.txt 2>&1; trc=$?
{ echo; echo "## W1 tester thread reads (tr.sh W1)"; cat $WIN/.w1-tr.txt; echo "# exit $trc"; } >> "$W"; cat $WIN/.w1-tr.txt
[ $trc = 0 ] || stop "W1 tester thread reads failed"
# The rollback source for the data-root files as they are just before W5 (PLAN.md W1).
bash $WIN/d3.sh > $WIN/d3-w1.txt 2>&1; drc=$?
grep -q D3_PASS $WIN/d3-w1.txt && [ $drc = 0 ] || stop "W1 d3.sh rc $drc: $(tail -3 $WIN/d3-w1.txt)"
note "W1 D3: $(sed -n 's/^D3 path //p' $WIN/d3-w1.txt), STAMP $(sed -n 's/^D3 STAMP //p' $WIN/d3-w1.txt), SHA256SUMS sha256 $(sed -n 's/^D3 SHA256SUMS sha256 //p' $WIN/d3-w1.txt), $(sed -n 's/^MEMBERS: //p' $WIN/d3-w1.txt)"
# W2
$WIN/w.sh W.txt "W2 status" scripts/stack-release.sh status mosaic-stack || stop "W2 status"
# W3
$WIN/w.sh W.txt "W3 activate NEW --pm --migrate --tester-socket --plan" scripts/stack-release.sh activate $NEW mosaic-stack --pm --migrate --tester-socket --plan > $WIN/W3-plan.txt || stop "W3 plan exit"
sed -i '$d' $WIN/W3-plan.txt
note "W3-plan.txt sha256 $(sha $WIN/W3-plan.txt)"
# The plan's W3 row: OLD to NEW, the migration lines (3m to 3n in one step), the backup line, and a Console line with OLD's flags exactly
# (--tester-socket, no --send, no --sso, no proof pair, no drop line). Any other difference stops here.
P3L=$WIN/W3-plan.txt
# The commit list holds subjects that name --sso and --send (row 273), so the flag checks read the plan without it.
P3N=$WIN/W3-plan-nocommits.txt
grep -v '^ [0-9a-f]\{9\} ' $P3L > $P3N
note "W3 commit list lines left out of the flag checks: $(grep -c '^ [0-9a-f]\{9\} ' $P3L)"
CONSOLE_WANT="Console: scripts/webui-service.sh install --copy $NEW --copies $HOME/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --tester-socket"
grep -qx "from: $OLD" $P3L || stop "W3: from is not OLD"
grep -qx "to: $NEW" $P3L || stop "W3: to is not NEW"
grep -qx "store schema: 3m (read from the store the running bus holds)" $P3L || stop "W3: store schema line"
grep -qx "target schema: 3n" $P3L || stop "W3: target schema is not 3n"
grep -qx "migration: 3m to 3n, after a backup of the stopped store" $P3L || stop "W3: migration line is not 3m to 3n"
grep -qx "migration steps: 3m to 3n" $P3L || stop "W3: migration steps are not 3m to 3n"
grep -qx "needs: --migrate (given)" $P3L || stop "W3: needs --migrate (given) line"
# The Jason line is expected; Q173 overrides decision 96 condition 2 for this business (sage-2703 item 2; PLAN.md P3 row).
grep -qx "needs: Jason present for this run (decision 96 condition 2)" $P3L || stop "W3: the decision 96 line is missing"
[ "$(grep -c '^needs: ' $P3N)" = 2 ] || stop "W3: a needs: line other than the two above"
[ "$(grep -c '^migration' $P3N)" = 2 ] || stop "W3: a migration line other than the two above"
grep -q '^restore: \|^note: ' $P3N && stop "W3: a restore or note line"
grep -qx "commits: the run adds $NCOMMITS (newest first, at most 200):" $P3L || stop "W3: commit count is not $NCOMMITS (git rev-list --count OLD..NEW)"
grep -qx "backup: the run backs up the stopped store to $DATA/backups/stack/<activation>/ before any migration" $P3L || stop "W3: backup line"
grep -qx -- "$CONSOLE_WANT" $P3L || stop "W3: Console line is not OLD's flags exactly"
[ "$(grep -c '^Console: ' $P3L)" = 1 ] || stop "W3: more than one Console line"
grep -q '^drop \|^sso: \|--send\|--sso\|--proof-header\|--proof-file' $P3N && stop "W3: a drop, sso, send or proof line or flag"
grep -qx 'proof: off' $P3L && grep -qx 'send: off' $P3L || stop "W3: proof and send lines are not off"
grep -q "^bus: scripts/bus-service.sh install --copy $NEW .* --pm " $P3L || stop "W3: bus line has no --pm on NEW"
note "W3 plan checks: OLD to NEW, 3m to 3n in one step, $NCOMMITS commits, needs --migrate (given) and the decision 96 line, backup line, Console line OLD's flags exactly, proof off, send off, no drop/sso/send/proof, bus --pm: PASS"
# W4
$WIN/w.sh W.txt "W4 bus unit (Environment omitted from the transcript)" systemctl --user show [email protected] -p ExecStart -p WorkingDirectory -p DropInPaths || stop "W4 bus"
$WIN/w.sh W.txt "W4 webui unit" systemctl --user show mosaic-webui.service -p ExecStart -p WorkingDirectory -p DropInPaths || stop "W4 webui"
BUSL=$(systemctl --user show [email protected] -p ExecStart -p DropInPaths -p WorkingDirectory)
WEBL=$(systemctl --user show mosaic-webui.service -p ExecStart -p DropInPaths -p WorkingDirectory)
for u in BUSL WEBL; do v=${!u}; grep -qx 'DropInPaths=' <<< "$v" || stop "W4 $u DropInPaths not empty"; grep '^ExecStart=' <<< "$v" | grep -q "copies/$OLD/" || stop "W4 $u ExecStart does not name the OLD copy"; grep '^ExecStart=' <<< "$v" | grep -q "copies/$NEW" && stop "W4 $u ExecStart names NEW"; grep '^ExecStart=' <<< "$v" | grep -q "[\"'\\\\]" && stop "W4 $u ExecStart has a quote or backslash"; done
grep '^ExecStart=' <<< "$BUSL" | grep -q -- ' --pm' || stop "W4 bus ExecStart has no --pm"
note "W4 checks: DropInPaths empty on both, both ExecStart name the OLD copy, bus has --pm, no quote or backslash in either ExecStart"
ENVL=$(systemctl --user show [email protected] -p Environment)
note "W4 bus Environment: $(tr ' ' '\n' <<< "${ENVL#Environment=}" | grep -c =) assignments; quote or backslash: $(grep -q "[\"'\\\\]" <<< "$ENVL" && echo FOUND || echo none) (values not recorded)"
note "W0b-W4 end $(date -u +%FT%TZ)"
+669
View File
@@ -0,0 +1,669 @@
# Row 270 build notes (Gizmoduck)
264c, T1 item 7: the compaction count, exact for Pi. Gitea #1775. Brief:
docs/plans/2026-10-21_t1-sessions.md section 4 (D6), with RULINGS Q6 (Pi exact, Claude `unknown` with
the Vikunja 466 reason). Reviewer Duckworth, gate owner Sage. Branch gizmoduck/row270 from
gizmoduck/row269 2d0d407ba (sage-2797, Q220). Row 269 lands first, and this branch rebases with it.
## Contract for 264e (pinned for Huey, row 272)
The surface reads the count from `session.list`. Each row gains one key, `compactions`:
```json
{ "id": "...", "at": "...", "name": "...", "predecessor": null, "default": true,
"compactions": { "count": 3, "known": true, "reason": null, "at": "2026-10-22T16:40:00.000Z" } }
```
- `known: true`: `count` is the exact number of completed compactions over the session's lifetime,
an integer of 0 or more. `reason` is null.
- `known: false`: `count` is a lower bound (an integer) or null when there is no bound. Show
`unknown` with the reason, never a bare 0. `reason` is one of:
- `harness-unsupported`: Claude Code, until Vikunja 466 pins its format.
- `not-collected`: no collection has run for this session yet.
- `store-unreadable`: a store file couldn't be read, or isn't a Pi session.
- `store-incomplete`: a stream file got shorter, was replaced, or has unreadable lines, so
entries may be missing.
- `at`: ISO time of the last collection, or null if none has run.
- A clear successor (264d) starts at `known: true, count: 0` once its first collection runs. The
retired session keeps its row and its count.
- On a base without 264c, the key is absent. The surface shows the count as unavailable, which is
what Huey's plan does (Sage Q1).
No new verb and no new input key. Access is `session.list`'s: the thread owner, or the tester.
## Pi source contract (pinned offline at 0.85.1)
`@earendil-works/pi-coding-agent` 0.85.1 (package.json pin), read in node_modules:
- One line per completed compaction: `{"type":"compaction","id":...,"parentId":...,"timestamp":...,
"summary":...,"firstKeptEntryId":...,"tokensBefore":...}` from `appendCompaction`
(dist/core/session-manager.js:818-833). The two callers write it after the summary exists
(dist/core/agent-session.js:1539, :1826). A start or an aborted compaction writes no entry.
- What doesn't count: a `message` entry with role `compactionSummary` (a rendered summary,
docs/session-format.md:154), any message inside a compaction's `retainedTail`, and
`branch_summary` entries.
- Identity: a compaction is its (entry `id`, entry `timestamp`) across the whole store, not per
stream. Pi's ids are unique only within a file (`generateId(this.byId)`, session-manager.js:21-29),
and `createBranchedSession` (:1093-1140) and `forkFrom` (:1264-1300) copy entries into a new
`*.jsonl` in the same directory with their id and timestamp unchanged. A (stream, id) key would
count a compaction made before a branch or fork once per copy. Reported to Sage in
gizmoduck-0262; accepted in sage-2814. The ledger line records the stream it was first seen in.
- Residual (sage-2814): two distinct compactions that share an id and a timestamp count once. That
needs an 8-hex collision between two files at the same millisecond. It is the one undercount
this key allows, and it never overcounts.
- Partial trailing line: wait. Only lines that end with a newline are read, as
packages/conversation/src/pi.mjs:35-38 does.
## Storage: A, a host ledger (sage-2806)
Sage ruled A over B. A keeps the count in `<store dir>/compactions.jsonl`, a file beside the
mounted `session/` and `claude-projects/`, in the store directory no bind mounts
(sandbox.mjs:122-127). B would have been a bus table. The tradeoff: A needs no schema step, so 271's
clear journal stays the only new schema step in the 264 rows, and the count lives with the native
store it is collected from. The cost is that the bus reads a file on each `session.list`, through a
reader runtime.mjs injects, because broker.mjs is a RUNNER_FILES file and can't import seat code.
### Ledger
`packages/seat/src/compactions.mjs`. One JSON object per line, `v: 1`:
- `{kind: "compaction", entry, completedAt, stream, at}`: once per (entry, completedAt) key.
- `{kind: "cursor", stream, bytes, tail, at}`: the stream was read through `bytes`. `tail` is the
sha256 of the up to 4096 bytes ending there. Written only when it moved.
- `{kind: "collected", at, known, reason, harness, version}`: one per collection.
Conditions (sage-2806):
1. Append-only, the launcher its only writer, a key never written twice, nothing rewrites or
truncates. The key is the one above, not (stream, id).
2. Mode 0600, opened `O_WRONLY|O_APPEND|O_CREAT|O_NOFOLLOW`. **Lock used: the session's workspace
lock** (`lockSessionWorkspace`, taken in launcher.mjs `start()` and released in the run's exit
`finally` or on a start failure). Collection runs at run start, after `check()` gives the
harness and before the spawn, and at run end, before the lock is released. Each collection is
one `writeSync` loop on one O_APPEND descriptor. A collection error is logged and recorded as a
`compactions` launch-log event and never stops or fails the run.
3. `compactionCount(dir)`:
| Ledger | Answer |
| --- | --- |
| missing | `not-collected`, count null |
| unreadable, not a regular file, a symlink | `store-unreadable`, count null |
| any bad line, a torn last line included | `store-incomplete`, count a lower bound or null |
| any collection under a harness other than pi | `harness-unsupported`, lower bound or null |
| any collection that found a stream shrunk, changed under its cursor or gone, or a bad stream line | `store-incomplete`, for good |
| the last collection hit an unreadable stream or a non-session first line | `store-unreadable` until a clean collection |
| otherwise | `known: true`, the exact count |
Never count 0 when unknown. After a torn line, the next write starts on a fresh line, so the torn
line stays one bad line and the new lines stay whole.
4. `packages/seat/tests/compactions.test.mjs` "the ledger is under no sandbox mount": for a legacy
and a non-legacy store, no `binds()` source contains the ledger path.
5. This section.
### Wiring
- launcher.mjs: `compactions(store, model.harness, ...)` at start (after `newRun`) and in the exit
`finally` before `lock?.release()`. Thread sessions only.
- broker.mjs: a `compactions` constructor option, TypeError-checked like `access`. `session.list`
selects `op` to tell a legacy session (`legacy-3l`) and adds `compactions` to each row; `op` is
not returned. No reader gives `not-collected`; a reader that throws gives `store-unreadable`.
No import added.
- runtime.mjs: passes `({business, thread, session, legacy}) => compactionCount(storeDir(...))`.
- packages/bus/README.md: the `session.list` row.
Huey's 272 broker hunk (reply citation predicate, near broker.mjs:5167 on huey/row272) doesn't
touch `session.list`.
## Tests
New files only, so no pre-existing test changes:
- packages/seat/tests/compactions.test.mjs: zero, a failed start and non-compaction entries; one and
three; a compaction copied into a branched stream counts once (sage-2814); one copy in two
streams in one collection; the same id at another time counts twice; resume with cursors and
keys written once; ledger mode 0600; a partial trailing stream line waits; a missing, a directory
and a symlinked ledger; a torn and a bad ledger line; shrink, rewrite and remove sticky; a
symlinked stream and a non-header first line; Claude harness-unsupported and sticky; `storeDir`
against `sessionStore`; the mount test.
- packages/bus/tests/session-compactions.test.mjs: the key per row with the store identity
asked for, legacy true for `legacy-3l`, no reader and a throwing reader, the TypeError.
Run limits (mos-1275): each file whole with `timeout -k 10 120`, TMPDIR from
`mktemp -d /mnt/storage/scratch/tmp/gXXX`. Then the touched files' suites: seat session.test,
sandbox.test; cli thread-launch, session-launch; bus sessions, project-runtime, tester-writes.
## Mutants
Each must turn a new test red:
- M1 key per stream at write: `keyOf(f.entry, f.completedAt)` → `keyOf(f.stream + f.entry, ...)` in
`collectCompactions`. The reader still dedupes, so the count holds; the kill is the
branched-copy test's ledger lines (`[["c1","a.jsonl"],["c2","b.jsonl"]]`) and the two-stream
test's one line.
- M2 key without the timestamp: `keyOf(entry, null)` everywhere. Kill: same-id-another-time test.
- M3 count `compactionSummary` messages: count `e.message?.role === "compactionSummary"` too. Kill:
the zero test.
- M4 ignore the cursor tail: `digest(tail) === c.tail` → `true`. Kill: rewrite subtest.
- M5 torn line skipped: `bad = torn = true` → `torn = true`. Kill: torn-line test.
- M6 no newline after a torn line: drop the `ledger.torn ? "\n"` prefix. Kill: torn-line test's bad
lines.
- M7 incomplete not sticky: drop `if (ledger.incomplete)`. Kill: the "later clean collection"
assertion.
- M8 harness not sticky: drop `if (ledger.unsupported)`. Kill: Claude then Pi test.
- M9 0 when unknown: `count: count || null` → `count`. Kill: torn-line test's null.
- M10 removed stream ignored: drop the missing-cursor loop. Kill: remove subtest.
- M11 broker legacy flag: `op === 'legacy-3l'` → `false`. Kill: bus legacy test.
- M12 broker default 0: no reader → `{count: 0, known: true, ...}`. Kill: bus no-reader test.
## Round 1 for review (sage-3024)
270's code reached the 271 branch in 09b20784f and 48c34539c and has no verdict and no mutant run.
This round submits it on its own: the manifest is the paths 270's commits change, hashed at
7d199f033 (the 271 r3 tip). queue-271/BUILD.md, which 8365e4e5b also touched, stays with 271.
broker.mjs, runtime.mjs and launcher.mjs carry 271's changes too, so their blobs match the 271 round.
Mutants: `mutants.py` beside this file, anchors at 7d199f033, each matching once (`check`, python
only). Each kill below is read from the test, not run yet.
| Mutant | Edit | Kill: first failing assertion |
| --- | --- | --- |
| M1 | collect key `keyOf(f.stream + f.entry, ...)` | seat compactions.test.mjs:64, ledger lines gain `[c1,b.jsonl]` (counts hold, readLedger dedupes) |
| M2 | `keyOf` drops the timestamp | :81, count 1 not 2 |
| M3 | count `compactionSummary` messages | :40, m2 counts 1 |
| M4 | tail digest check `true` | :170 in `rewrite`, known true |
| M5 | `bad = torn = true` to `torn = true` | :131, the torn line is popped unparsed, count reads known 0 |
| M6 | no newline after a torn line | :133, c1 joins the torn line, count null not 1 |
| M7 | drop `if (ledger.incomplete)` | :174 in `shrink` (`remove` stays incomplete by its missing cursor) |
| M8 | drop `if (ledger.unsupported)` | :201, Pi run reads known |
| M9 | `count \|\| null` to `count` | :131, count 0 not null |
| M10 | drop the missing-cursor loop | :170 in `remove` |
| M11 | broker `op === 'legacy-3l'` to `false` | bus session-compactions.test.mjs:76 |
| M12 | broker no reader gives `{count: 0, known: true}` | bus session-compactions.test.mjs:81 |
Run plan, on a CLEAR only: each mutant runs its test file whole under `timeout -k 10 120` (M1-M10
seat compactions.test.mjs, M11-M12 bus session-compactions.test.mjs), TMPDIR from
`mktemp -d /mnt/storage/scratch/tmp/gXXX`, restore with `git checkout` and a clean-tree check after
each. A kill is an AssertionError at the named line. M11 and M12 edit broker.mjs, which 271 also
changed, so after their restore bus session-clear.test.mjs and sessions.test.mjs run whole, each
under `timeout -k 10 300`. compactions.mjs has no 271 change.
Not covered: launcher.mjs's two `compactions(store, ...)` calls (run start, run end) and runtime.mjs's
reader closure have no test in either file, and no test elsewhere names compactions. A mutant
dropping either launcher call or making the closure answer a constant would survive. This is the
T3 Pi half as built; the Tests section above never claimed the wiring. Adding a cli and a runtime
test is a scope call for Sage.
Prior coverage: none. 271's M1-M14 edit no 270 line, and no past verdict names 270.
## Round 2 (duckworth-0223 B1-B3, sage-3030, sage-3031, mos-1352)
New commits on 8a5eb3c8b (round 1, on 7d199f033). Nothing here has run: no test, no mutant, no node.
Each kill below is read from the test.
### B1: the read budget, a human-only setting
`scanPi` reads a stream a CHUNK (1 MiB) at a time from its cursor, carries a line split across
reads, and stops when the collection's budget runs out, over all streams together. The whole-range
`Buffer.alloc(to - from)` that compactions.mjs:57 and :101 had is gone from the stream read;
`readRange` now serves only the tail digest reads, at most 4096 bytes. A collection the budget stops
short records `known: false`, reason `not-collected`, and cursors at the last whole line it read. The
reason isn't sticky: `compactionCount` takes `last.reason`, so a later collection that reads to the
end reads known again. When a collection has more than one problem the order is store-incomplete,
store-unreadable, then not-collected.
The setting (mos-1352, DECISIONS Q222). It's `compactionReadBytes`, a top-level key of the instance's
system config, the file `MOSAIC_CONFIG` names (`~/.config/mosaic-NAME/config.json`). Under invariant 2
a human writes that file and nothing auto-writes it. The launcher reads it once, at host start,
through `loadSystem`; nothing reads it from the session mount, the workspace or an environment
variable an agent's process sets. `readBudget` (compactions.mjs) takes a whole number of bytes from
1 MiB to 1 GiB and gives the default for anything else, missing included. The launcher logs a bad
value once at start, naming the default. A change takes effect at the next host start.
The schema change, for review. `scripts/mosaic-config.mjs` refused any top-level key outside
`configVersion, environment, dataRoot, execution, console` (`unsupported configuration key`). It now
also takes `compactionReadBytes`, and passes it on as written, a bad value included, because the
fallback is the launcher's and a bad value must not refuse the config. The output carries the key
only when the file has it, so a config without it prints what it always did (test-config.sh's
"config without console" case still pins that). The old list literal stays as it was and the key is
added by `.concat`, because packages/cli/tests/stack-release-fixture.mjs:300 edits that literal to
make an older loader.
A consequence for Duckworth to rule on. A loader from before this round refuses a config that sets
the key. scripts/stack-release.sh `config_checks` (:1079-1097) runs the target copy's loader only when
the config has a `console` key. So a human who sets `compactionReadBytes` and then rolls back to a
copy from before 270 passes that check, and the bus fails at its next config read. Nobody has to set
the key, since the default needs none. I've left stack-release.sh alone: it's outside this row's
manifest. The bounded follow-up is to make `config_checks` run the target loader whenever the config
has any key the target might refuse, not just `console`.
The default, 64 MiB, from sizes measured 2026-10-22 by stat and line length only (no content printed,
nothing under the live stack read):
| Store | Files | p50 | p90 | p99 | max |
| --- | --- | --- | --- | --- | --- |
| Pi sessions, `~/.pi/agent/sessions` | 224 | 298 KB | 1.1 MB | 4.7 MB | 11.6 MB |
| Claude Code transcripts, `~/.claude/projects` (not read by the scanner) | 549 | 607 KB | 6.6 MB | 120 MB | 603 MB |
The longest Pi line is 0.84 MB, and no Pi line is over 1 MiB. 64 MiB is more than five times the
largest Pi stream, so a session's first collection reads it whole, and later ones read only what
was appended. Memory per collection is one 1 MiB chunk plus the carry of one unfinished line, and
the budget bounds both. The Claude numbers show how far a long agent session can grow. A Pi session
that size would take several collections to read through, each one reading unknown until the last.
I haven't measured how long a 64 MiB collection takes; the sparse test reads 64 MiB of a hole, and its
wall time in the slot run will be the first number.
Known limits:
- A single line longer than the budget is never read past. The count stays not-collected until a
human raises the setting. The longest Pi line measured is 0.84 MB.
- `readLedger` still reads the ledger whole; B1 named the stream reads. Ledger lines are 120 bytes
(collected), 226 (cursor) and 199 (compaction), so a run with two collections, one changed stream
and one compaction adds about 0.9 KB. One session reaches 64 MiB of ledger after about 75,000 runs.
### B3: Claude history from before the ledger
When a Pi collection finds no ledger and the store's `claude-projects/` (`store.projects`) isn't
empty, it skips the scan and writes one collected record: `known: false`, reason
`harness-unsupported`, harness `pi`. `readLedger` now sets `unsupported` for a record whose harness
isn't `pi` or whose reason is `harness-unsupported`, so the count stays unknown for good, as after a
Claude run. A `claude-projects/` that can't be read throws, so nothing is recorded and the next
collection checks again. An empty one is no history. Once a ledger exists the check doesn't run: a
later Claude run collects for itself.
A Pi run mounts `claude-projects/` read-write (sandbox.mjs:125). The run-start collection comes before
the agent's first turn, though, so an agent can't put a file there before a fresh session's first
collection. The exception is a run whose start collection failed. Even then, the agent can only
make the count unknown, never wrong.
The Vikunja 466 reason. For a Claude run, seat compactions.test.mjs:199 asserts `harness-unsupported`,
and :201 asserts that it stays after a Pi run. For history from before the ledger,
compactions-budget.test.mjs:88 and :89 assert it (sage-3030 item 2).
### B2: the run wiring, the reader and a clear
The new tests live in files 271 doesn't touch:
- `packages/seat/tests/compactions-budget.test.mjs`, B1 and B3.
- `packages/cli/tests/compactions-launch.test.mjs`, with its stub adapter
`packages/cli/tests/fixtures/compaction-adapter.mjs`, real runners in bwrap.
compactions-launch tests:
1. A run's start and end collections, across a resume and two host restarts. session.list reads the
ledger through the runtime's reader (:166-198).
2. A collection that fails, with the ledger's path made a directory. Each failure is logged as
`run X: compactions: ...` and recorded as `store-unreadable`. The run answers, and it ends with the
same reason, exit code and signal as the next run, whose collections count (:200-224).
3. The setting. At 1 MiB, a 2 MiB hole is not-collected at both collections. With `"x"`, the host
logs the fallback once, and the default reads the hole through (:226-261).
4. A clear. The retired session keeps its count, 1, and the successor reads `{count: 0, known: true}`
after its first collection. A legacy session (op legacy-3l, seeded with the SQL from
bus session-compactions.test.mjs:72-74) reads the thread store's ledger through the runtime's reader
after a restart (:263-308).
### Mutants M13-M21
`mutants.py`, with all 21 anchors matching once at this round's tip (`check`, python only).
| Mutant | Edit | Kill: first failing assertion |
| --- | --- | --- |
| M13 | drop the run-start `compactions(...)` call (launcher.mjs:501) | cli compactions-launch.test.mjs:182, run 1 logs only `[D,2,true,null]` |
| M14 | drop the run-end call (launcher.mjs:642) | :182, run 1 logs only `[D,1,true,null]` |
| M15 | the runtime's reader returns `null` (runtime.mjs:268) | :197, compactions null; :286 and :306 fail the same way |
| M16 | no budget, `stop = size` | seat compactions-budget.test.mjs:48, the hole is read whole: known true |
| M17 | `readBudget` returns `value ?? READ_BYTES` | :37, `"8388608"` comes back as the string |
| M18 | the launcher ignores the setting, `budget = READ_BYTES` | cli :248, the 2 MiB hole reads known twice |
| M19 | no carry, `take(view.subarray(i, nl))` | seat :79, c1's halves fail to parse: store-incomplete |
| M20 | no Claude history check | :88, c1 counts known |
| M21 | `harness-unsupported` not sticky on a `pi` record | :89, the second collection reads known |
The files they touch, sha256 at this round's tip, to check each restore against (sage-3030 item 3):
```
45a3744598cfb8aa1cfcffd8a0ee2dd2bb631061efd192d8461f1901141993ee packages/seat/src/compactions.mjs
d7cdc8a17e337e00dfe2d1bf6249cc2c2e696b16c5b426b2fe616089e5559509 packages/bus/src/broker.mjs
5d36cc0fa19ab8e478ecf9a807e3afab8181cb650778e80b13efe785e6c44995 packages/cli/src/launcher.mjs
5ae423438a178b1c8eac3b8ce0e0c22f5bb7caebb9011ce7098a36d95b53f1ca packages/bus/src/runtime.mjs
```
broker.mjs and runtime.mjs are byte-identical to 7d199f033.
### Run plan, on a CLEAR only
Every run uses TMPDIR from `mktemp -d /mnt/storage/scratch/tmp/gXXX` and `timeout -k 10`. A first-run
red in a new test is placed and reported after the run (mos-1272). A red in an old test, a product
fix or a second red goes to Sage and Mos first.
1. Each mutant runs its kill file whole: M1-M10 seat compactions.test.mjs, M16, M17 and M19-M21 seat
compactions-budget.test.mjs (120 s each), M11-M12 bus session-compactions.test.mjs (120 s), and
M13-M15 and M18 cli compactions-launch.test.mjs (600 s). After each one: `git checkout` the file,
check its sha256 against the table, and confirm the tree is clean. A byte-identical restore needs
no other rerun.
2. The new test files, whole: seat compactions-budget.test.mjs and cli compactions-launch.test.mjs.
3. Every file that reads a path this round touches, whole:
- compactions.mjs: seat compactions.test.mjs, bus session-compactions.test.mjs.
- mosaic-config.mjs: scripts/test-config.sh, cli stack-instances.test.mjs, cli stack-release.test.mjs
(through stack-release-fixture.mjs), webui pinned-copy.test.mjs, and the business suite (it
writes configs through helpers.mjs:79).
- launcher.mjs: the cli files that import it, all in the cli suite.
- README.md: no test reads it.
4. The gate (sage-3031) runs on the combined 270+271 tip in one slot after Gander's 276 run5. It
covers bus, seat and cli whole, the shell suites, M1-M21 and 271's mutants. compactions.mjs is in
271's manifest, so 271's earlier gate evidence is stale. Webui pinned-copy and the business suite
join it because the loader changed.
### Known residuals (duckworth-0223, sage-3031)
- N1. A session.list read can briefly see a half-written line. The bus reads without the workspace
lock, so a read during an append sees a torn last line. That call alone reports store-incomplete,
and nothing is persisted. Accepted.
- N2 (README). A launcher killed mid-write leaves a torn ledger line, and the count stays unknown
(store-incomplete) for good, by design (sage-2806 condition 3). This is now a line in the bus
README's `session.list` row.
- N3. Pi 0.85.1 rewrites a session file only when it's empty (session-manager.js:625-632), on a
version migration (:675-677), or for a new branched file (:1173). None of these moves bytes under a
0.85.1 cursor, so the 4096-byte tail check is enough. A non-Pi writer that rewrites earlier bytes and
keeps the tail goes unseen. That residual is acceptable.
## Round 3 (duckworth-0226 B4, sage-3038)
Round 2 at 62a279cf3 got CHANGES on B4. B1-B3 were met, and Duckworth accepted the schema change.
### B4: a cap on the streams one collection opens
Before this round, one collection opened every `*.jsonl` in the session directory. It also checked
each ledger cursor with `names.includes`, which is O(cursors × names), and wrote a cursor line for
every stream, empty ones included. The agent can write its rw session directory (sandbox.mjs:124),
and nothing limits its file count. So 100,000 empty files meant about 22 MB of cursor lines at one
run end, then about 10^10 string compares on the launcher's loop at the next.
Now:
- scanPi (compactions.mjs:123-125) sorts the `*.jsonl` names and opens at most `streams` of them.
When there are more, the collection adds not-collected. The streams it didn't open get no cursor
line. A stream that already has a cursor keeps its old one.
- The missing-stream check uses a Set (compactions.mjs:121-122).
- The cap is a setting, per sage-3038 (the same reason as mos-1352 and Q222): top-level
`compactionStreams` in the instance system config. It follows compactionReadBytes. It is
human-only. `readStreams()` accepts a safe integer from 32 to 65536 and returns the default, 1024,
for anything else. The launcher reads it once and logs one `system config:` line for a bad value.
mosaic-config.mjs adds the key to the same `.concat` and passes it on verbatim. The fixture literal
at stack-release-fixture.mjs:300 still matches.
- Name order stays sorted, so a collection under a larger cap, or with fewer files, catches up.
Under the same cap and the same files, each collection opens the same streams and adds no cursor
lines for streams that haven't moved.
Default, from stat only (`find ~/.pi/agent/sessions -name '*.jsonl'`, counted per directory, no
content read). Each of these directories holds every session for one cwd, so a single session's
count is at most these:
| directories | p50 | p90 | p99 | max |
|---|---|---|---|---|
| 40 | 3 | 19 | 27 | 27 |
1024 is about 38 times the max. The cursor lines one collection writes are bounded by 1024 × 226 B,
about 231 KB. At the setting's 65536 maximum the bound is about 14.8 MB.
### N4: docs/TOOLS.md
docs/TOOLS.md has a paragraph on both keys, after the `config-console-key` paragraph. It gives the
range, the default, that only a human writes them, the fallback with its log line, not-collected,
and C1. No test reads TOOLS.md. stack-release.test.mjs:78 only names it inside a message string.
### C1 (duckworth-0226, sage-3038)
Any re-pin or rollback plan whose OLD copy is before row 270 removes both `compactionReadBytes` and
`compactionStreams` from config.json first. A pre-270 loader refuses either key. stack-release.sh
config_checks (:1079-1097) runs the target loader only when there is a `console` key, so the release
doesn't catch this. Sage is drafting the config_checks follow-up row.
### Tests
- seat compactions-budget.test.mjs:114-120: readStreams over good and bad values.
- :122-134: four streams under a cap of 3. Both collections are not-collected, and the cursors are
only a, b and c, with no new cursor lines on the second. A cap of 4 then reads count 4, known.
- :136-143: 1025 empty streams under the default cap. The result is not-collected with 1024 cursor
lines, the last one e1023.
- cli compactions-launch.test.mjs:264-298: compactionStreams 32 with 34 streams. pi.jsonl sorts
last, so its c0 isn't opened and both collections are `[D,null,false,not-collected]`. Then `"x"`
logs the fallback, and the next run's collections read `[D,1,true,null]`.
- scripts/test-config.sh: 2048 and `"x"` load and pass through, as for compactionReadBytes.
### Mutants
M10's anchor moved to the Set line. The mutant is the same, removing the missing-stream check. Every
anchor in mutants.py matches once (`check`). Round 3 shifted round 2's kill lines: seat budget
:37/:48/:79/:88/:89 are now :39/:50/:81/:90/:91, and cli launch :182/:197/:248 are now
:183/:198/:249. mutants.py K has the current lines. The launcher call sites are now
launcher.mjs:507 and :648.
| id | change | kill |
|---|---|---|
| M22 | no cap, `for (const name of visit)` | seat compactions-budget.test.mjs:127, all four are opened: count 4 known; :141 fails the same way |
| M23 | `readStreams` returns `value ?? STREAMS` | :118, `"2048"` comes back as the string |
| M24 | the launcher ignores the setting, `streams = STREAMS` | cli compactions-launch.test.mjs:285, all 34 open: `[D,1,true,null]` twice |
sha256 at this round's tip, to check each restore against. These replace round 2's table:
```
b5a078a096e447c4afe15812696d1f2f8c3f2b7665868c02e61a4e1edc8cd874 packages/seat/src/compactions.mjs
d7cdc8a17e337e00dfe2d1bf6249cc2c2e696b16c5b426b2fe616089e5559509 packages/bus/src/broker.mjs
21a7e910d81848aeb99036028d5b87354a2a515f3642779105c881b43a8b6e05 packages/cli/src/launcher.mjs
5ae423438a178b1c8eac3b8ce0e0c22f5bb7caebb9011ce7098a36d95b53f1ca packages/bus/src/runtime.mjs
```
### Run plan
This is round 2's plan with M1-M24. M22 and M23 run seat compactions-budget.test.mjs (120 s). M24
runs cli compactions-launch.test.mjs (600 s). Per sage-3038, the slot goes to Megavolt's 207, then
203, then this row's combined 270+271 gate after Duckworth approves r3.
### Residuals
- N5. readdirSync still lists the whole session directory, and the names are sorted before the
cap. Both are O(files) in memory and time on the launcher. The cap bounds opens, reads and ledger
lines, not the listing. Bounding the listing needs opendirSync with a stop. That would lose name
order, and name order is what lets a later collection catch up. Accepted for this row
(duckworth-0226).
- The ledger's cursor map still grows when the agent deletes and recreates streams, by at most the
cap per collection. Each deletion of a stream with a cursor is store-incomplete for good. This is
the known limit that the bus reads the ledger whole (round 2).
## Round 4: rebase onto row 203 (sage-3073)
Rows 270 and 271 are rebased onto origin/refactor 25c09f19f (sage-3078): row 203 (FED R2) at
a4da4e6df, plus three docs-only commits for the O2 design (BUILD-LOG.md, docs/SESSIONS.md and
docs/plans/2026-10-21_q195-writing-editor.md, none of them in either manifest). The new branch is
gizmoduck/row270-271-r4. gizmoduck/row271-r2 stays at b9da7697f. The old
merge-base was 0cbe2e9b3, and the rebase moved 28 commits.
`git range-diff 0cbe2e9b3..b9da7697f 25c09f19f..HEAD~1` shows 26 commits
unchanged. Round 2 and round 3 differ, and only in scripts/mosaic-config.mjs:
- The allowlist keeps 203's literal and adds both keys after it:
`["configVersion", "environment", "dataRoot", "execution", "console", "federation"].concat(["compactionReadBytes", "compactionStreams"])`.
203 changed packages/cli/tests/stack-release-fixture.mjs, and its LOADER_OLD replace is now at
:301 (rounds 2 and 3 say :300). It matches the literal with `"federation"`, which is still
intact before the `.concat`.
- The output has 203's `federation` line, then the two `Object.hasOwn` lines.
Rows 203, 270 and 271 also all touch scripts/test-config.sh, scripts/stack-release.sh and
packages/cli/tests/stack-release.test.mjs. Those merged without conflict, and the hunks are far
apart:
- test-config.sh: 203's federation cases start at :194. Mine are at :266-:281, after the
no-console case.
- stack-release.sh: 203 changed config_checks at :1078-:1097. Row 271's change is COUNTED at
:420, still the line 271's BUILD.md cites.
- stack-release.test.mjs: 203 changed :1468 and :1582. Row 271's LISTED is still at :861 and T46
at :913.
203 widens config_checks to run the target loader for a `federation` key as well as `console`.
It still does not run the loader for compactionReadBytes or compactionStreams, so C1 stands as
written.
`mutants.py check` passes: every anchor matches once. The restore hashes in round 3's table are
unchanged, because 203 touches none of those four files. No test pins a sha256 of mosaic-config.mjs,
test-config.sh, stack-release.sh or stack-release.test.mjs.
Nothing has run on this base yet. The combined gate is sage-3073 item 4, on Sage's CLEAR.
## Combined 270+271 gate (C2), written before the first run (sage-3073 item 4, sage-3086, mos-1351)
The gate runs on the commit that adds this section. Its code is ac44d0de9's: `git diff --name-only
ac44d0de9 HEAD` is this file only. The review keys stay manifest-270-r4 4f5e7041… and manifest-271-r4
3ab1f5c8…, Duckworth approves at revs 3379 and 3380. Worktree /mnt/storage/scratch/gizmo-270, with
node_modules linked to the canonical checkout. Base 25c09f19f. Script
/mnt/storage/scratch/gizmo-271-out/run-r4.sh, run with pipefail and tee, logs in
/mnt/storage/scratch/gizmo-271-out/r4/. TMPDIR comes from `mktemp -d /mnt/storage/scratch/tmp/gXXX`.
### Blob table
sha256 of every source file a mutant edits, at the gate commit. Each restore is `git checkout --` the
file, then this hash, then a clean tree.
```
b5a078a096e447c4afe15812696d1f2f8c3f2b7665868c02e61a4e1edc8cd874 packages/seat/src/compactions.mjs (270)
d7cdc8a17e337e00dfe2d1bf6249cc2c2e696b16c5b426b2fe616089e5559509 packages/bus/src/broker.mjs (270, 271)
21a7e910d81848aeb99036028d5b87354a2a515f3642779105c881b43a8b6e05 packages/cli/src/launcher.mjs (270, 271)
5ae423438a178b1c8eac3b8ce0e0c22f5bb7caebb9011ce7098a36d95b53f1ca packages/bus/src/runtime.mjs (270)
```
### Timeout table (mos-1275)
Every run is `timeout -k 10 LIMIT`. The longest records are 271's gate on e8af389e6 (BUILD.md queue-271
"Gate on e8af389e6") and 271's "Run limits" table.
| Run | Longest recorded | LIMIT |
| --- | --- | --- |
| stack-release.test alone | 575 s | 1800 s |
| each other touched test file alone, stack-instances.test, webui pinned-copy.test | 15 s | 300 s |
| business suite | none recorded | 900 s |
| bus suite | 24 s | 900 s |
| seat suite | 6 s | 600 s |
| cli suite | 591 s | 1800 s |
| each scripts/test-*.sh | 42 s (test-foundation; 39 s at e8af389e6) | 900 s |
| 270 mutant, seat or bus kill file | none recorded | 120 s |
| 270 mutant, cli compactions-launch.test | none recorded | 600 s |
| 271 mutant, each session-clear.test file | 57 s for both files (e8af389e6) | 300 s |
A shell-phase timeout goes to Mos unplaced. A timeout with no failed test is a hang and goes to Sage.
### GREEN and RED
- GREEN for a test run: rc 0, `fail 0`, and at least one pass for a single file. A skip is named in the
report.
- RED: any other rc, a failed test, or zero passes.
- A mutant is killed when its kill file is RED with a `not ok` on the test K names. 270's K is in
queue-270/mutants.py. 271's kills are the bus or cli session-clear.test failures in queue-271/BUILD.md.
A mutant whose kill file stays GREEN survived.
- A first-run red in a new test (seat compactions-budget.test, cli compactions-launch.test, 271's new
tests) is placed and reported after (mos-1272). A red in an old test, a product change, a looser
assertion or a second red on a row stops the run and goes to Sage before any change (sage-3086).
- Mutants run only on a GREEN base.
### Order
1. The 20 touched test files whole, then cli stack-instances.test, webui pinned-copy.test and the
business suite (the config loader's readers, round 2 run plan item 3).
2. bus, seat and cli suites whole.
3. The ten scripts/test-*.sh, test-config.sh among them, with skips named.
4. 270 M1-M24, each on its K file whole. Then 271 M1-M14, each on bus and cli session-clear.test whole.
### Run 1: a first-run red, placed (mos-1272)
Run 1 started 00:06:57Z on 0f5e5d62d. In phase 1, the 15 bus files and session-compactions passed.
Then cli compactions-launch.test.mjs, new in this row and on its first run, went RED: 4 of 5 pass. The
failure was in the clear test, "REQ-THREAD-2 T3 T4 a clear leaves the retired session's count...", at
:319 (now :324):
`[done?.state, done?.start]` was `[undefined, undefined]`, not `["retired", "started"]`, after the
30 s settle (31433 ms).
The cause is in the test, not the product. The test's host `ops` table had no entries for row 271's
five clear ops: sessionClears, sessionClearExited, sessionClearUncertain, sessionClearRetire and
sessionClearStart. The launcher's clear calls through host.op were refused as invalid-host-request,
so the successor never started. cli session-clear.test.mjs:96-100 wires the same five, and its clear
tests passed in this run (5 of 5).
The placement adds those five lines, copied from session-clear.test.mjs, after landRelease. No assertion
changed, and no product file changed. The file alone then passed 5 of 5
(/mnt/storage/scratch/gizmo-271-out/place-compactions-launch.log). The five lines move 270's
compactions-launch kill lines by +5 in queue-270/mutants.py K: :188, :203, :254 and :290. Round 2
and round 3 above cite the old lines.
Run 1 stopped at 00:07:59Z (the log's last write), after phase 1's cli session-clear.test, so that the gate runs on one
tree. No node process was left, and the tree was clean. Its log is
/mnt/storage/scratch/gizmo-271-out/r4-run1/. This is the row's first red. Run 2 is the whole gate
again on the commit that adds the placement. That commit changes the 270 manifest (compactions-launch.test.mjs,
mutants.py and this file), so Sage and Duckworth see it before landing.
### Run 2: a second red, two test defects (mos-1364, sage-3093)
Run 2 started 00:09:50Z on 5f697db99. Phase 1 passed except one file: the 15 bus files, session-compactions
4/4, compactions-launch 5/5, cli session-clear 5/5, stack-release 219/219, seat compactions 20/20,
stack-instances 24/24, webui pinned-copy 10/10 and business 231/231. seat compactions-budget.test.mjs
went RED: 8 of 10 pass. The file is new in round 2, and this was its first run. Rounds 2 and 3 were not
run. This is the row's second red, so I stopped the run (r4.out's last write 00:20:12Z) and changed
nothing until Mos ruled yes on both fixes (mos-1364, passed on in sage-3093). Phase 2 did not start. Its
log is /mnt/storage/scratch/gizmo-271-out/r4-run2/.
Both failures are defects in the test, not in compactions.mjs.
F1, :39 in "the budget is the setting's whole number of bytes...". `readBudget(8.5 * MiB)` returned
8912896, not the default 67108864. 8.5 MiB is 8912896 bytes: a whole number, in range. readBudget
(compactions.mjs:80-82, Number.isSafeInteger and the 1 MiB to 1 GiB bounds) is right to take it. The case
meant a value that is not a whole number of bytes. The fix changes only that input, to `MiB + 0.5`. The
other twelve bad values stay. M17's kill line is this :39, so M17 had no valid kill before the fix.
F2, :107 in "a claude-projects/ that can't be read...". The hook `t.after(() => chmodSync(projects,
0o700))` threw ENOENT. A test's after hooks run in the order they were registered. setup()'s rmSync of the
root (:20) was registered first, so the tree was gone when this hook ran. The test's assertions had passed.
The fix puts the 0o000 window in try/finally, which restores 0o700 before the rm even when an assertion
fails, and drops the hook and the inline chmod. It keeps the same assertions in the same order, and none
is removed. Net +2 lines, so in mutants.py K, M23 moves :118 to :120 and M22 :127 to :129. M22's note
cites the default-cap test's :141, now :143. M15's note cites the clear test's :323 and the legacy :343,
which are :328 and :348 since run 1. That change is to note text only. `mutants.py check` shows all 24
anchors matching once.
The file alone then passed 10 of 10 (/mnt/storage/scratch/gizmo-271-out/place-compactions-budget.log).
Run 3 is the whole combined gate again, on one tree, on the commit with F1 and F2. A third red on 270
stops it and goes to Sage and Mos before any change.
### Runs 3 and 4: two old-test races, not 270 or 271 (mos-1367, mos-1369)
Run 3 started 00:23:35Z on 53406f3e4. Phase 1 passed whole (business 231/231), bus 873/873 and seat
202/202. cli went RED, 904 of 905: case1-accept.test.mjs:368, the P10 inner hop probe took 1.08 s, under
its 1.25 s floor. The file is old and unchanged in 25c09f19f..53406f3e4. I stopped the run during phase 3
(r4.out's last write 00:46:24Z) and changed nothing. Mos ruled (mos-1367, passed on in sage-3097): A1 run
case1-accept alone, which passed 50/50 (00:50:01Z to 00:54:17Z, load 3.19 3.58 3.71); A2 run 4 on the
same tip. Vikunja 573 (owner gander) owns the P10 band fix. Log: /mnt/storage/scratch/gizmo-271-out/r4-run3/.
Run 4 started 00:54:25Z on 53406f3e4 (load 4.45 4.20 3.94). Phase 1 passed 23/23 files and bus 873/873.
seat went RED, 201 of 202: checkout.test.mjs:239 "a dirty clone whose fetch fails is kept, fetch-failed,
exactly as it was" failed :91 "the root is unchanged" at its first once(w) (:241). The file is old and
unchanged in 25c09f19f..53406f3e4, and it passed in run 3. Load was 8.19 at 01:06Z. I stopped the run
(r4.out's last write 01:06:31Z) and changed nothing. The cause is the fixture, not 270 or 271: its git
helper (checkout.test.mjs:35) runs with gitEnv() (checkout.mjs:80-90), which does not set
maintenance.auto=false, so its commit can leave a detached `git maintenance run --auto` writing in the
root that once() fingerprints. Mos watched objects/maintenance.lock vanish after commit returned in 1 of
30 fresh repos (mos-1369, passed on in sage-3106). Vikunja 574 (owner gander) owns the fixture fix; no test
changed in this slot. Log: /mnt/storage/scratch/gizmo-271-out/r4-run4/.
### Run 5: GREEN (mos-1369, sage-3106)
Run 5 waited until the one-minute load was under 4, then started 01:17:11Z on 53406f3e4, unchanged since
run 3 (loadavg 3.86 4.91 4.69, up 9 days 11:37; origin/refactor 062646487, no drift from 25c09f19f in
packages or scripts). It ended 01:49:41Z, wall 1950 s, exit 0.
| Phase | Result | Wall |
| --- | --- | --- |
| 1, touched test files whole and the loader's readers | 23/23 files pass; stack-release 219/219, compactions-budget 10/10, business 231/231 | 673 s |
| 2, bus suite | 873/873 | 25 s |
| 2, seat suite | 202/202 | 7 s |
| 2, cli suite | 905/905; P10 inner hop probe 2.31 s, outer 4.14 s | 682 s |
| 3, scripts/test-*.sh | all ten pass; tree clean after | |
| 4, 270 M1-M24 | 24/24 killed, each on its K file, each restore matched | |
| 4, 271 M1-M14 | 14/14 killed in the file the 271 table names (271 BUILD.md:713), each restore matched | |
No zero-pass file. Three skips, the same as before: test-queue's queue verify and render --check (this
checkout is not the queue's canonical root) and test-task's live provider cases, twice (row 244). No node
process was left with its cwd in the worktree. Sage ran one `git fetch` from the canonical checkout at
about 01:38:10Z, inside the cli suite (sage-3122); the suite had no red. Log:
/mnt/storage/scratch/gizmo-271-out/r4/ and r4.out.
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
# Row 270 mutants. M1-M12 are round 1's (anchors at 7d199f033); M13-M21 are round 2's
# (duckworth-0223 B1-B3, sage-3030, sage-3031, mos-1352); M22-M24 are round 3's (duckworth-0226
# B4, sage-3038). Round 3's kill lines are at round 3's tests. `apply <id>` edits the worktree and
# prints the touched path; each edit asserts its anchor appears exactly once. `check` prints each
# anchor's count and kill. K names the test and the first assertion each mutant should fail
# (checked by reading, not run). The base blob of every touched file is in BUILD.md; a restore is
# `git checkout -- <path>`, then sha256 against it (sage-3030 item 3).
import sys
WT = "/mnt/storage/scratch/gizmo-270/"
C = "packages/seat/src/compactions.mjs"
B = "packages/bus/src/broker.mjs"
L = "packages/cli/src/launcher.mjs"
R = "packages/bus/src/runtime.mjs"
ST = "packages/seat/tests/compactions.test.mjs"
SB = "packages/seat/tests/compactions-budget.test.mjs"
BT = "packages/bus/tests/session-compactions.test.mjs"
CT = "packages/cli/tests/compactions-launch.test.mjs"
M = {
"M1": (C, " const key = keyOf(f.entry, f.completedAt);\n if (ledger.events.has(key)) continue;",
" const key = keyOf(f.stream + f.entry, f.completedAt);\n if (ledger.events.has(key)) continue;"),
"M2": (C, "const keyOf = (entry, completedAt) => JSON.stringify([entry, completedAt]);",
"const keyOf = (entry, completedAt) => JSON.stringify([entry, null]);"),
"M3": (C, 'if (e.type === "compaction" && typeof e.id === "string" && e.id) {',
'if ((e.type === "compaction" || e.message?.role === "compactionSummary") && typeof e.id === "string" && e.id) {'),
"M4": (C, "digest(tail) === c.tail) from = c.bytes;", "true) from = c.bytes;"),
"M5": (C, 'if (lines.pop() !== "") bad = torn = true;', 'if (lines.pop() !== "") torn = true;'),
"M6": (C, '(ledger.torn ? "\\n" : "") + out.map', 'out.map'),
"M7": (C, ' if (ledger.incomplete) return unknown("store-incomplete", count, at);\n', ""),
"M8": (C, ' if (ledger.unsupported) return unknown("harness-unsupported", count, at);\n', ""),
"M9": (C, "({ count: count || null, known: false, reason, at })", "({ count, known: false, reason, at })"),
# Round 3 moved M10's anchor to the Set (duckworth-0226 B4); the mutant is the same.
"M10": (C, ' for (const stream of Object.keys(cursors)) if (!present.has(stream)) problems.add("store-incomplete");\n', ""),
"M11": (B, "op === 'legacy-3l')", "false)"),
"M12": (B, "if (!this.#compactions) return unknown('not-collected');",
"if (!this.#compactions) return { count: 0, known: true, reason: null, at: null };"),
# B2: the launcher's two collections and the runtime's reader.
"M13": (L, " if (store) compactions(store, model.harness, { instance, thread, session, run });\n const toolSocket",
" const toolSocket"),
"M14": (L, " if (store) compactions(store, model.harness, { instance, thread, session, run });\n try { lock?.release(); }",
" try { lock?.release(); }"),
"M15": (R, " return dir ? compactionCount(dir) : { count: null, known: false, reason: 'store-unreadable', at: null };",
" return null;"),
# B1: the budget, its setting and the carry across reads.
"M16": (C, " const stop = Math.min(size, from + left);", " const stop = size;"),
"M17": (C, " return Number.isSafeInteger(value) && value >= READ_MIN && value <= READ_MAX ? value : READ_BYTES;",
" return value ?? READ_BYTES;"),
"M18": (L, " const budget = readBudget(system?.compactionReadBytes);", " const budget = READ_BYTES;"),
"M19": (C, " take(carry.length ? Buffer.concat([...carry, view.subarray(i, nl)]) : view.subarray(i, nl));",
" take(view.subarray(i, nl));"),
# B3: Claude history from before the ledger, and its stickiness.
"M20": (C, ' if (harness === "pi" && !prior && claudeHistory(store)) {', ' if (harness === "pi" && !prior && false) {'),
"M21": (C, 'if (r.harness !== "pi" || r.reason === "harness-unsupported") unsupported = true;',
'if (r.harness !== "pi") unsupported = true;'),
# B4: the stream cap and its setting.
"M22": (C, ' if (visit.length > streams) problems.add("not-collected");\n for (const name of visit.slice(0, streams)) {',
' for (const name of visit) {'),
"M23": (C, " return Number.isSafeInteger(value) && value >= STREAMS_MIN && value <= STREAMS_MAX ? value : STREAMS;",
" return value ?? STREAMS;"),
"M24": (L, " const streams = readStreams(system?.compactionStreams);", " const streams = STREAMS;"),
}
K = {
"M1": (ST, "a compaction copied into a branched stream counts once, under the stream it was first seen in", 64,
"c1 is written again under b.jsonl: [[c1,a],[c1,b],[c2,b]]; the count holds at 1 and 2 because readLedger dedupes"),
"M2": (ST, "the same entry id at another time in another stream is another compaction", 81, "count 1, not 2"),
"M3": (ST, "an empty store, a store with no compaction and a failed start count 0, known", 40,
"the compactionSummary message m2 counts: count 1, not 0"),
"M4": (ST, "a stream that shrank, changed under its cursor or went away is store-incomplete for good > rewrite", 170,
"same length, other bytes, read from the old cursor: known true"),
"M5": (ST, "a torn last ledger line makes the count unknown, a lower bound, and stays so", 131,
"the torn line is popped unparsed, so nothing sets bad: {count 0, known true}"),
"M6": (ST, "a torn last ledger line makes the count unknown, a lower bound, and stays so", 133,
"the c1 record joins the torn line and is lost: count null, not 1"),
"M7": (ST, "a stream that shrank, changed under its cursor or went away is store-incomplete for good > shrink", 174,
"the later clean collection reads known; remove would pass, its cursor stays missing"),
"M8": (ST, "a Claude run is harness-unsupported, and stays so after a Pi run", 201, "the Pi run reads known true"),
"M9": (ST, "a torn last ledger line makes the count unknown, a lower bound, and stays so", 131,
"count 0, not null; the symlinked-stream test's :184 fails the same way"),
"M10": (ST, "a stream that shrank, changed under its cursor or went away is store-incomplete for good > remove", 170,
"a removed stream raises no problem: known true"),
"M11": (BT, "a legacy session is asked for with legacy true", 76, "asked with legacy false"),
"M12": (BT, "without a reader the count is unknown, never 0; a reader that throws is store-unreadable", 81,
"{count 0, known true} for no reader"),
"M13": (CT, "a run collects compactions at its start and its end, and the count holds across a resume and a host restart", 188,
"no start collection: run 1 logs only [D,2,true,null]; settle returns after 5 s with one event"),
"M14": (CT, "a run collects compactions at its start and its end, and the count holds across a resume and a host restart", 188,
"no end collection: run 1 logs only [D,1,true,null]"),
"M15": (CT, "a run collects compactions at its start and its end, and the count holds across a resume and a host restart", 203,
"session.list's compactions is null: {at: 'undefined'} and no count; the clear test's :328 and the legacy :348 fail the same way"),
"M16": (SB, "a stream past the default budget is not-collected with a lower bound, and a later collection catches up", 50,
"the whole 66 MiB is read: {count 1, known true, reason null}"),
"M17": (SB, "the budget is the setting's whole number of bytes from 1 MiB to 1 GiB, and the default for anything else", 39,
"readBudget(null) is the default, but readBudget('8388608') is the string"),
"M18": (CT, "the read budget is the system config's compactionReadBytes, and a bad value takes the default", 254,
"the 1 MiB setting is ignored, the 2 MiB hole is read whole: [D,1,true,null] twice"),
"M19": (SB, "a line split across reads counts once", 81,
"c1's two halves parse alone and fail: {count null, known false, reason store-incomplete}"),
"M20": (SB, "Claude history from before the ledger makes the first Pi collection harness-unsupported, for good", 90,
"the first collection counts c1: {count 1, known true}"),
"M21": (SB, "Claude history from before the ledger makes the first Pi collection harness-unsupported, for good", 91,
"a Pi collected record isn't sticky: the second reads {count 1, known true}"),
"M22": (SB, "more streams than the cap is not-collected, with cursors only for the streams opened, and a larger cap catches up", 129,
"all four streams are opened: {count 4, known true, reason null}; the default-cap test's :143 fails the same way (1025 cursors)"),
"M23": (SB, "the stream cap is the setting's whole number from 32 to 65536, and the default for anything else", 120,
"readStreams(null) is the default, but readStreams('2048') is the string"),
"M24": (CT, "the stream cap is the system config's compactionStreams, and a bad value takes the default", 290,
"the cap of 32 is ignored, all 34 streams are opened: [D,1,true,null] twice"),
}
cmd = sys.argv[1]
if cmd == "list":
print(" ".join(M)); sys.exit(0)
if cmd == "check":
for k, (f, a, b) in M.items():
n = open(WT + f).read().count(a)
tf, name, line, why = K[k]
print(k, f, n, "kill", f"{tf}:{line}", name)
sys.exit(0)
f, a, b = M[sys.argv[2]]
s = open(WT + f).read()
assert s.count(a) == 1, (sys.argv[2], s.count(a))
open(WT + f, "w").write(s.replace(a, b))
print(f)
+790
View File
@@ -0,0 +1,790 @@
# Row 271 build notes (Gizmoduck)
264d, T1 item 7: clear context through row 194's lifecycle stop. Gitea #1776. Brief:
docs/plans/2026-10-21_t1-sessions.md section 5 (D7, D8), with RULINGS Q4 (rebase onto 194's
landing, no 194 code copied). Reviewer Duckworth, gate owner Sage. Built after row 270 (sage-2797).
## Contract for 264e (pinned for Huey, row 272)
Proposed, pending Duckworth's review. Any change goes to Huey before it's pushed.
### session.clear
Input, every key required, with no other key (`keys()` as `session.create` does):
```json
{ "thread": "<thread id>", "session": "<session id>", "revision": 0, "confirm": true, "op": "<op key>" }
```
- `revision`: the integer from the session's `session.list` row (below). It names the state the
human confirmed.
- `confirm`: the literal `true`. Anything else refuses `confirm-required`.
- `op`: the caller's operation key, an identifier, unique per thread. It is also the idempotency
key.
Answer, the operation envelope:
```json
{ "op": "...", "thread": "...", "session": "<old id>", "successor": "<new id>",
"state": "clearing", "start": null, "refusal": null, "at": "..." }
```
- `successor` is bound when the request is accepted, before any asynchronous work. It is a
lowercase UUID, `xxxxxxxx-xxxx-8xxx-Vxxx-xxxxxxxxxxxx` with V one of `8`, `9`, `a`, `b` (RFC 9562
version 8, variant 10). It's the first 32 hex digits of sha256(business, thread, old session, op)
with those two nibbles set. It matches the UUID test every session selector uses
(cli/src/thread.mjs:41), so 264a's selectors and deep links take it unchanged (Huey's question,
gizmoduck-0274).
- `state`:
- `clearing`: accepted. Sends and launches to the old session are fenced, and the drain or stop
is in progress.
- `uncertain`: stop proof isn't available. Capacity stays reserved and nothing is forced. It
resumes when proof arrives.
- `retired`: the old session is retired and the successor exists, in one bus transaction.
- `failed`: no retirement happened, and `refusal` names why. The old session stays as it was.
- `cancelled`: a human cancelled the clear (session.cancel below, sage-2828). `retired` is
null if it was cancelled before retirement and true if after. `start` stays as it was.
- `start` is null until `state` is `retired`. After that it is `held` (waiting for capacity),
`started`, or `refused` with `refusal` set.
- `at`: the time of the latest state change.
Repeats and conflicts:
- The same `op` with the same payload returns the current envelope and starts nothing new.
- The same `op` with any other payload refuses `session-op-conflict`.
- A new `op` on a session with an open clear refuses `session-clearing`.
- A `revision` that isn't current refuses `session-revision-stale`.
- Other refusals: `session-not-found`, `session-retired`, `processing-halted` (D8: a halt refuses
and changes nothing), and the owner refusals `session.create` already gives.
### session.cancel (sage-2828)
Input, every key required, with no other key:
```json
{ "thread": "<thread id>", "op": "<the clear's op>", "confirm": true }
```
This is a human write with the same owner or write-grant check as session.clear, rechecked under
the session fence. An agent caller refuses as session.clear does. `confirm` must be the literal
`true`, or it refuses `confirm-required`. The answer is the operation envelope with `state`
`cancelled`.
- Before retirement, cancel stops the replacement:
- The successor id is never created or started.
- Messages already cancelled stay cancelled and are never replayed.
- The old session is `active` again.
- After retirement, cancel stops the startup only:
- No further start attempts are made.
- Both sessions stay as they are. Nothing un-retires.
- Repeats and refusals:
- A repeat with the same payload returns the original cancelled envelope.
- The payload digest covers `{thread, op}` and the calling principal. A cancel from a
different principal, or a payload that otherwise differs, refuses `session-op-conflict`.
- An op that isn't a clear refuses `operation-not-found`.
- A cancel of a clear whose successor has started refuses `session-clear-complete`.
### session.operation
Input `{ "thread": "...", "op": "..." }`. Answer: the same envelope. Unknown op refuses
`operation-not-found`. A CLI that waits 30 s and then reports pending uses this to poll. A retry
queries the op here and never resends the clear.
### session.list additions
Each row gains:
- `state`: `active`, `clearing`, `uncertain` or `retired`. A cancelled clear leaves the session
`active` if cancelled before retirement, and `retired` if after.
- `revision`: an integer. 0 at creation, plus 1 for each lifecycle event on that session.
- `successor`: the successor's id once retired, otherwise null.
A successor row has `predecessor` set (264a's column). When the cleared session was the thread's
default, `default` moves to the successor at retirement. Clearing any other session leaves the
default where it is.
On a base without 264d, `session.clear`, `session.cancel` and `session.operation` refuse
`unknown-verb`, and the rows have no `state`,
`revision` or `successor`.
## Design (draft 2: Sage's sage-2826 rulings and Mos's mos-1315 folded in)
Measured on 2d0d407ba, which already holds row 194 (ab7377bcf). Nothing has been run. Draft 1
was 3105de889. Draft 2 changes where the drained acknowledgement comes from (stage 2): threadIdle
alone can't show that no child is running.
### What exists and what doesn't
- No per-session lifecycle state. `sessions` is append-only (schema.sql:370-372). The nearest
per-session state is the latest `thread_claims` row (op and generation). Row 194's lifecycle
is per instance, as `action.allowed` events keyed by a random request id (broker.mjs:622-666).
- No cancelled state for an undelivered message. `deliveries.op` is a CHECK list on an
append-only table.
- The runner calls `message.receive` only when no harness child exists:
- `runTurn`'s close handler SIGKILLs the turn's whole process group and clears `child`
(harness/src/runner.mjs:363-366).
- The loop asks receive only after the landing turns (:493-517) and after each message's
RESULT and `message.read` (:524-580).
- So a receive from the run is a completed turn boundary, made by the runner itself.
- `threadIdle` (broker.mjs:865-877) is not enough. A landing turn reads no message, so `unread`
is 0 while its child runs. That is why landRelease keeps a run until it has asked receive
(`#received`, broker.mjs:363-365, :3815-3829).
- There is precedent for acting at receive. `#limitStop` (broker.mjs:4147-4148, :4499-4520)
holds a stop until the run's next `message.receive` or `state`, so the turn's RESULT and read
land first.
- The runner's SIGTERM handler escalates to KILL after 5 s only when `child` is set
(runner.mjs:405-423). With no child it sets `stopping`, the loop ends, and it exits 0.
- The launcher's idle stop (launcher.mjs:683-701) sends a SIGTERM after a start-time check. The
exit proof is the run's `close` handler, which runs endRun, removes the registry entry and
releases the workspace lock (:600-641).
### Clear, stage by stage
1. **Accept** (`session.clear`, the human's write). One transaction:
- Check owner and grant (the `session.create` path), the revision, and that processing isn't
halted.
- Bind the successor before any async work:
- The id is deterministic, from a sha256 of (business, thread, old session, op), shaped as a
lowercase version 8 UUID (the contract above). 264a's ids are UUIDs, and every selector
checks the UUID shape, so draft 2's `ID_PATTERN` shape is replaced.
- The name is `<old name>-N`, with N the next free number in that thread. If a user already
made a session called `<old>-N`, the next free N is taken.
- Both go on the `requested` row, so a repeat of the op returns the same id and name.
- Append journal stage `requested`, with a payload digest for the repeat and conflict checks.
- The answer is `clearing`. From here:
- `thread.send` and `thread.open` naming the session refuse `session-clearing`. So does a
thread-only send whose default is that session.
- `threadsState` reports the session as `clearing`, so `threadPass` never starts it.
- Undelivered messages to it are cancelled with a receipt each (`message_cancels`), in the
same transaction. Old messages, results and run evidence don't change.
2. **Drain, acknowledged by the runner.** Once the session is clearing, the run's
`message.receive` (the runner's call, at a turn boundary with no child) takes one transaction:
- It appends `drained` with the run and its claim generation, once.
- It delivers nothing and answers `[]`.
- The claim stays held. That holds the dequeue fence, so no turn and no child can start
before the stop, and the old capacity stays reserved.
Later receives keep answering `[]`. `state` needs no fence, because the runner asks it only
for a message it received. A clear on a session with no holder appends `drained` with run
null at accept.
3. **Stop and proof** (launcher). For a `drained` op whose run and generation are still the
holder's, the launcher sends the idle stop's start-time-checked SIGTERM, the same reviewed
path. The runner has no child, so its handler never reaches KILL.
- The exit proof is the run's own `close` handler (endRun, registry remove, lock release),
recorded with `sessionClearExited {business, op, run}`. A signal, a revoked claim or a dead
socket is not proof.
- Under the host, the runner is pid 1 of its own pid namespace (`--unshare-pid --as-pid-1`),
so its exit ends anything a turn left outside its process group. **This is a build check**,
tested against bwrap, not assumed.
- With no run, the proof is that the launcher takes `lockSessionWorkspace` for the session and
the registry has no run for it.
- If no `drained` arrives (a turn still running, or a stuck child), the session stays
`clearing`. Capacity stays reserved, the successor stays unstarted, the status stays
actionable, and no signal is sent.
- Thirty seconds after the SIGTERM with no proof, `uncertain` is appended once. The launcher
keeps waiting and sends no second signal. A launcher restart finds the run among its row 269
survivors and waits on its identity.
4. **Retire** (trusted op `sessionClearRetire {business, op}`), one transaction:
- Check the exit proof (the run is in `#ended`), or that there was no run.
- Append `retired`, and end the old claim.
- Insert the successor `sessions` row with the bound id and name, `predecessor` set to the old
id and `op` set to the clear op.
- Add a `session_defaults` row only if the old session was the default.
- Add a `thread_opens` row for the successor.
The old workspace stays at its path with no new writer mount. The successor gets its own store,
HOME and workspace from row 269's per-session layout, and a fresh checkout of the approved
revision. Nothing is copied.
5. **Start** (D8). `threadPass` starts the successor from its `thread_opens` row through the same
`check()`/`start()` as any session. The launcher reports the outcome with `sessionClearStart
{business, op, start, refusal}`, which appends `start-held`, `start-started` or
`start-refused`, each with the next report counter. A held start keeps its op and spends no
work attempt.
`failed`: a refusal at retire (for example, the owner lost the grant) appends `failed` with the
code. Nothing is retired, and the old session goes back to `active` with its messages still
cancelled.
**Cancel** (sage-2828, re-ruling sage-2826 Q3, D7:263-264). `session.cancel` checks the owner or
write grant under the fence and appends `cancelled`, attempt 0, in one transaction:
- Before `retired` it releases the fence:
- If no `drained` was recorded, the drain is still waiting. Nothing is signalled, the clear
ends, and the run's next receive delivers again.
- If `drained` was recorded with the claim still held and no SIGTERM sent, the launcher skips
the stop because the op is no longer open. The run keeps its claim, and its next receive
delivers again.
- If the SIGTERM was already sent or the run has exited, the old session is `active` with no
runner. The next send launches it through D8 like any idle session.
- The successor row is never inserted, and the cancelled messages stay cancelled.
- The launcher reads the op state right before its SIGTERM, in the same tick as its start-time
check. A cancel after that point still finds a stopping runner, which takes the "active with
no runner" path above. This is a residual: a cancel can't take back a SIGTERM already sent.
- A pre-retire cancel doesn't touch the claim (duckworth-0171 C2):
- A run that still holds it keeps it, generation and all. Releasing the fence only stops its
receives answering `[]`.
- A run already stopped ended its own claim through endRun, so there's nothing to change.
- Messages already cancelled keep their `message_cancels` rows and are never delivered.
- After `retired` and before `start-started`, no further start attempts are made.
- The successor row and the default move stay. Nothing un-retires the old session.
- threadPass doesn't start the successor from the clear's `thread_opens` row: a cancelled op
suppresses that open.
- **My reading, stated for Sage:** the successor is then an ordinary idle session. A later new
send to it, including a thread-only send while it's the default, launches it through D8 like
any idle session. That's new input, not the clear's startup. The other reading leaves the
thread's default pointing at a session that can never start, because there is no verb to
move a default (the session verbs are list and create).
- The race with the drained receive: `drained` and `cancelled` are each one synchronous
transaction on the open op. Whichever commits second sees the first. A receive after the
cancel finds no open op and delivers normally. A cancel after `drained` takes the "drained, no
SIGTERM yet" path above.
- After `start-started` it refuses `session-clear-complete`.
### Early look d2 folds (duckworth-0170, 98dc69f0c)
- **B1, messages routed by `#sessionFor` (broker.mjs:722-748).** A role reply chain or a spec
notice can resolve to a clearing or retired session with no state check. Rule: **cancel with a
receipt at arrival.** The message is inserted (old results stay as evidence, D7 step 4) and its
`message_cancels` row goes in the same transaction, so it is never delivered and never pending.
The send's answer gains `cancelled: "session-clearing"` or `"session-retired"`, an additive key.
- Refusing was the other option. It would lose a teammate's RESULT outright: the runner only
logs a failed reply (harness/src/runner.mjs:575-580).
- Routing to the successor would replay old input, which D7 forbids.
- A spec notice resolves through `#defaultSession`. During a clear of the default session that
is still the old session, so the notice is cancelled. After retirement the default is the
successor, and a new notice is new input that goes there.
- `threadsState` reports `retired`, and `threadPass` starts neither a `clearing` nor a
`retired` session.
- **B2, a claim racing a no-holder clear.** role.claim's thread path (broker.mjs:4827-4845)
refuses `session-clearing` for a session with an open op and `session-retired` for a retired
one. The runner gets refused and exits 21. Retire rechecks in its own transaction that no
`claim` is current for the session, unless it's the proven-ended run's. The no-run proof also
takes `lockSessionWorkspace`, which a spawned run holds before it claims.
- **B3, op namespace.** `sessions` has `UNIQUE(thread, op)` (schema.sql:263), and the successor
takes the clear op. Both directions refuse `session-op-conflict` at accept, before any stop:
- `session.clear` refuses an op already in `sessions` or `session_ops` for the thread.
- `session.create` (broker.mjs:5859-5872) refuses an op already in `session_ops`, instead of
answering the successor row.
- **B4** is in the schema step below (the `attempt` key).
- **N2, name cap.** `name()` caps names at 200 characters (broker.mjs:323). When `<old>-N` would
be longer than that, session.clear refuses `session-name-too-long` at accept and writes nothing
(sage-2828). Test: a 199-character name clears to a 201-character `<old>-1` and is refused, and
a 198-character name clears.
- **N3, batch.** Drain waits for the runner's whole receive batch. Messages already delivered
are a turn in progress, and the runner runs each of them before its next receive (:543-590).
Only undelivered messages are cancelled. This is a residual: a batch of k delivered messages
delays the drain by k turns.
- **N4, D7 step 1 rechecks.** Retire rechecks owner, write grant and halt in its transaction, and
refuses to `failed` with the code. External effects: the old run's endRun record is the stop
evidence the op joins (its run and generation are on `drained`). No other effect is reconciled
in 271.
- **N5, claim end.** Retire ends the old claim only if that run's `claim` is still the latest
`thread_claims` row for the session. If endRun already ended it, nothing is written twice. Test:
retire after endRun, with exactly one end row.
- **N6, pid 1.** `--as-pid-1` is in `NAMESPACES` (seat/src/sandbox.mjs:68), and `sandboxArgv`
uses it (:168) for the runner command that session.mjs:302 builds. At build I'll confirm that
thread runs reach session.mjs:302. Teardown on exit is kernel behaviour, and the test stays.
- **N7**: Duckworth checks the mos-1315 list line by line at the code round.
- **N8**: row 206 is approved at 3l→3m, so if it lands first, 271 is 3m→3n (as mos-1315 item 4
says).
Cancel tests (sage-2828, duckworth-0171 C2):
- before retirement (no drained yet; and drained with the claim held)
- a cancel racing the drained receive, each order
- a cancel after `start-held`: no further report, and threadPass doesn't start the successor
- held, held, then started on one op gives three start rows with reports 1-3 and one work
attempt (C1)
- after retirement, with no further start
- an idempotent repeat
- a changed payload refused
- after completion: `session-clear-complete`
- an agent caller refused
Mutant: drop the fence release in cancel's pre-retire path. The kill is the pre-retire test's next
send/receive.
Tests gained: B1 a cancel at arrival (reply chain and spec notice) for clearing and retired; B2
a claim refused while clearing, and retire refusing a live claim; B3 both op directions; B4 a
second `start-held` appended; N2; N5.
duckworth-0171 N1: the `cancelled` key on message.send is additive. At build I'll grep the
whole-answer `deepEqual` pins on message.send in `packages/*/tests/*`. A pin breaks only where a
test's routed message lands on a clearing or retired session, which no test does before 271.
Any hit goes into the mos-1315 list first.
### Schema step (label by landing order, mos-1315 item 4)
- `session_ops(seq, at, business, thread, session, op, stage, attempt, successor, successor_name,
run, generation, digest, refusal)`:
- append-only, with `UNIQUE(thread, op, stage, attempt)` and a CHECK on `stage`
- the stages are `requested`, `drained`, `uncertain`, `exited`, `retired`, `failed`,
`cancelled`, `start-held`, `start-started` and `start-refused`
- `attempt` is 0 for every stage up to `retired`, and for `failed` and `cancelled`. For the
three start stages it is a report counter: 1 plus the number of start rows the op already
has. Every `sessionClearStart` report bumps it, a held report included. This keys D7's
retryable start (duckworth-0170 B4, duckworth-0171 C1).
- The launcher's work attempt goes in its own `work_attempt` column, null outside the start
stages. A held start spends none (D8, t1-sessions.md:274), so held, held, started share one
work attempt and take reports 1, 2 and 3.
- The stage CHECK pins `attempt = 0` for every non-start stage, `cancelled` included.
- `session_ops_one_open` treats an op as open while its latest stage is `requested`, `drained`,
`uncertain` or `exited`. `retired`, `failed` and `cancelled` close it, so a new clear on the
same session can follow a cancelled or failed one. A cancelled op stays in the namespace (B3).
- a trigger refuses a second open op per session
- the envelope comes from the latest stage, and `revision` is the count of this session's rows
- `message_cancels(seq, at, message UNIQUE, session, op)`, append-only.
- `threadRows`'s `pending` gains `AND NOT EXISTS (cancel)`, and each row gains `state`.
- Label: 3m if 271 lands before row 206. If 206 lands first (3l to 3m at Launchpad's 631a256ba),
271 rebases on it and is 3m to 3n. Sage sets the order.
### Pre-existing test changes (mos-1315)
These are allowed:
- the current schema label
- the fabricated future step in comment-migration.test.mjs, moved up one label
- the migration file lists, which gain the new step's file
No assertion is removed or loosened, and no other expected value changes. This list was measured
on 2d0d407ba with `git grep` for `'3l'`, `3k-3l` and `'3m'` in `packages/*/tests/*`.
- Current label (`'3l'` becomes the new label):
- account-migration.test.mjs:94, :144, :163, plus the rewrite source :156
- chief-of-staff.test.mjs:641, plus the rewrite source :617
- comment-migration.test.mjs:36
- gitea-migration.test.mjs:98, :124, :142, plus the rewrite source :135
- grant-migration.test.mjs:103, :128, :146, plus the rewrite source :139
- name-migration.test.mjs:68
- sessions.test.mjs:377, :450, plus the rewrite source :433
- spec-migration.test.mjs:66
- thread-migration.test.mjs:104, plus the rewrite source :52
- usage-migration.test.mjs:71
- A rewrite source is the `.replace("export const SCHEMA_VERSION = '3l';", ...)` string. It has
to match store.mjs:17's new text, or the rewrite silently does nothing.
- Fabricated future step: comment-migration.test.mjs:78-79, :86 (`3l`→`3m` becomes one label up).
- Migration file lists:
- comment-migration.test.mjs:82
- the `copyFileSync` lists that copy `3k-3l.sql`: account-migration.test.mjs:86,
gitea-migration.test.mjs:76, grant-migration.test.mjs:79
- plus each one's sibling copy loop, read whole at the edit
- sessions.test.mjs:435 asserts which step names the edited source holds. If its expected list
needs the new step's file, that falls under the migration-file-list allowance. I'll read it
whole at the edit.
- **Restore lists (mos-1318 item 1).** Both new tables have an `at` column, so both join
`COUNTED` in scripts/stack-release.sh:420. That is product scope for 271: a rollback with
`--restore` counts their rows written after the backup and refuses on them, as it does for
`sessions` and `thread_opens`. T46 (stack-release.test.mjs:913) then passes unchanged. Neither
table goes in `untimed`, and nothing is removed from any list.
- `session_ops`: COUNTED. Every write is a clear stage, and a restore that dropped one uncounted
could leave a retired session's successor without its journal.
- `message_cancels`: COUNTED. A dropped cancel would put a cancelled message back in `pending`.
- T45's `LISTED` (stack-release.test.mjs:858) is a hand copy of COUNTED, so that a table dropped
from the script fails T45. **Both names are added** (mos-1319). Mos counts this as the
stricter change mos-1285 allows, because T45's loop then checks each new table on its own.
The reviewer checks that LISTED matches COUNTED name for name: 15 names after 271, where
there were 13. On this base COUNTED is at stack-release.sh:420. Mos measured it at :418 on
aca16404c.
- **Count pins (mos-1318 item 2).** schema.test.mjs:525-527 (table, trigger and view counts):
- tables 22 to 24: `session_ops`, `message_cancels`
- triggers 86 to 93:
- `session_ops_no_update`, `session_ops_no_delete`, `session_ops_no_replace`
- `session_ops_one_open` (refuses a second open op on a session)
- `message_cancels_no_update`, `message_cancels_no_delete`, `message_cancels_no_replace`
- views: 5, unchanged
- The comment block above the pins gains a row 271 line, as rows 124-264a have.
- If the build adds or drops an object, this list changes first and the reviewer checks it.
- If row 206 (631a256ba) lands first, 271 re-adds its own objects on top of 206's counts.
- Any other count pin found at build goes here before it's edited.
- After the move, grep `packages/*/tests` for the old label. Only history fixtures and migration
chains may still name it. The result is recorded here (mos-1315 item 3).
- **TESTER_VERBS pin (mos-1325 item 1).** tester-writes.test.mjs:28-29 gains one comment line and
the three names after 264a's `session.list`, `session.create`. The deepEqual stays exact.
- **Engines that load the whole current store** need the new step's file. That's the
migration-file-list allowance. account-, gitea- and grant-migration.test.mjs each gain a
`copyFileSync` of `3l-3m.sql` after their `3k-3l.sql` copy, for their `engine-failing`, which
keeps every step. The engines that truncate the step list (engine-3h, -3j, -3i, -3c, -3d, -3k)
need nothing.
- Applied on gizmoduck/row271 (edit script gizmo-271-edits/mos1315.py). The grep for `'3l'` in
`packages/*/tests/*` afterwards has no hit. `3k-3l` stays in the migration copy lists and in
sessions.test.mjs:435's "no 3k→3l step" assertion, both history.
- Comment-only mentions (`3k-3l migration` in end-launch, message-filter, thread-claims, threads,
session-launch, thread-cli, thread-launch, chat, session-compactions) are history and stay.
### Tests (T4, to write)
- clear on a held session:
- accept fences the session
- the run's next receive appends `drained` and answers `[]`
- SIGTERM, then exit proof, `retired`, the successor row and the default move
- a landing-turn run (no receive yet) is never drained or signalled
- a run whose turn is running (delivered, unread) stays `clearing` with no signal, past 30 s
- a non-default session clears with the default unchanged
- `<old>-N` collision: a user session already named `<old>-1` gives `<old>-2`
- repeat with the same payload gives the same envelope, id and name
- conflict: a changed payload refuses `session-op-conflict`
- a second op on a clearing session refuses `session-clearing`
- stale revision, halted processing, retired session
- undelivered messages are cancelled with receipts, not delivered to the successor
- sends to a clearing session refuse
- `uncertain` after 30 s with no proof, and no second signal
- the successor start is held, started or refused, through check()/start()
- the pid-namespace build check
## Build round 1 (code on gizmoduck/row271, not run: the slot was held)
Where the code departs from the design above. Each one is for Duckworth at the code round.
- **No `state` key in `threadsState`.** end-launch.test.mjs:193 pins a whole threadsState row, and
a new key would be an old-test change. Instead:
- `threadRows` drops retired sessions (broker.mjs `#threadRows`), and a cancelled message isn't
`pending`.
- A new trusted op, `sessionClears {business}` (broker.mjs:988), lists each open or starting
clear: thread, instance, session, op, stage, run, generation, successor, refusal. The
launcher reads it once per tick in `clearPass` (launcher.mjs:742), before its threads loop.
Any session a listed clear owns (the old session while stopping, the successor while
starting) is skipped by the normal start and idle code, so threadPass never starts or idles
it.
- **No `thread_opens` row at retire.** The successor's start comes from `sessionClears` (stages
`retired`, `start-held`, `start-refused`), not from an open. A cancelled op isn't listed, so
nothing starts its successor. A later new send gives the successor a `pending` message, and
threadPass starts it like any idle session (sage-2830's reading).
- **The run's end is a drain too.** `endLaunch` appends `drained` (run, generation) when the
ending run's session has a `requested` op. A run that exits before its next receive needs no
signal.
- **Exit proof.** `sessionClearExited {business, thread, op, run}` refuses `run-not-ended` unless
the run has ended. With run null, the launcher first takes and releases the session's workspace
lock. A busy lock (a spawning run) throws and the clear waits a tick.
- Side effect: `sessionWorkspace` creates the session's workspace directory, so a session that
never ran gets an empty one at its clear.
- **Restart.** `recover()` SIGKILLs an earlier host's runs and ends them `host-lost`. A run that
outlived the kill is a row 269 survivor and is ended too. `clearStop` waits while a survivor
with the clear's run is live (`liveRun`), and sends it nothing. When the old run is gone, the
ended run is the proof.
- `clearStops` is in memory, so a restarted launcher has no stop times. That's safe because
recover() has already killed or listed every run from the earlier host, so none is in the new
registry to signal. An `uncertain` op it finds is never signalled.
- **Halt at retire waits.** `sessionClearRetire` throws `processing-halted` while processing is
halted, and the op stays `exited` until it lifts. Only an owner or grant refusal appends
`failed`.
- **Held codes.** A successor start refused `capacity-full` or `limit-reached` reports `held`.
Every other refusal reports `refused`, and any non-Refusal throw reports `launch-failed`. Each
code is logged and reported once per op, not once per tick.
- **Names stay bound.** `#nameTaken` counts any op's `successor_name`, a cancelled or failed one
included. So a session.create of `<old>-1` after a cancelled clear refuses `session-exists`, and
the next clear takes `<old>-2`. That's stricter than needed. Freeing a name would let a repeat
of the cancelled op's envelope name a session that isn't its successor.
- **Cancel after `failed`** refuses `session-clear-complete`, like cancel after `start-started`.
- **message.send** answers `cancelled: "session-clearing"` or `"session-retired"` only when its
route lands on such a session (B1). N1 grep of whole-answer pins is still to do.
### Tests added to T4 (sage-2830)
- After a post-retire cancel, a thread-only send to the thread (the successor is the default)
launches the successor through D8 as new input. The clear's latest row stays `cancelled`, and
no `start-*` row is appended.
- After a restart, a live survivor of the cleared run holds the clear at `drained` with no
signal. Its exit lets the clear retire.
### Tester grant (mos-1325)
The brief's section 3 says tester writes aren't granted by default and use fixtures only. 271
grants the tester `session.clear`, `session.cancel` and `session.operation` because the Q173 live
check has to clear a tester thread through the tester socket. Without the grant, D7 can't be
proved live by an agent tester.
- The three verbs act only on the tester's own threads. Each resolves its thread with `#thread`
(broker.mjs:871-877), which refuses `thread-not-owned` unless the thread's owner is the caller's
`#owner`, and a tester's owner is `tester` (:879-880). So a tester can't reach a human's or a
user's thread.
- Test: a tester's `session.clear`, `session.cancel` and `session.operation` on a thread a human
owns each refuse `thread-not-owned` and write nothing (no `session_ops` or `message_cancels`
row).
- The R3 user gate refuses all three for a user (user-principal.test.mjs:133 spreads
TESTER_VERBS). Those refusals are this row's own checks (mos-1325 item 3).
### N1 and N6 at build
- **N1.** message.send adds `cancelled` only when `#route` returns a code (broker.mjs:5505,
`if (cancelled) return { id: mid, request, cancelled }`). Every other send answers exactly what
it did before. `#route` returns a code only for a session with an open or retired clear, and
before 271 no test can make one. So no whole-answer pin on message.send can change, and nothing
joins the mos-1315 list.
- **N6.** The launcher's only spawn is `spawnSession` at launcher.mjs:574, inside `start()`
(:422), which runs every session, thread runs included. spawnSession (seat/src/session.mjs:300-302)
builds the runner argv through `sandboxArgv`, whose argv starts with `NAMESPACES` (sandbox.mjs:168),
and `NAMESPACES` holds `--unshare-pid` and `--as-pid-1` (:68). The bwrap test stays in T4.
### Tests written (round 1, not run: the slot was held)
New files only. No old test changes beyond the mos-1315 list above.
- `packages/bus/tests/session-clear.test.mjs` (15 tests), the bus half:
- held default: accept fences send, open and claim; the receive drains once; no proof before the
run's end; retire, successor row, default move; threadsState drops the retired session
- unheld session: drained at accept, proof with run null; a non-default clear keeps the default
- a run that ends before its next receive drains at its end, with its generation
- delivered and unread: stays `clearing`, `not-drained` for uncertain and proof; uncertain once;
later proof resumes
- B1: undelivered inputs cancelled at accept with receipts; a routed reply answers `cancelled`
`session-clearing`, later `session-retired`; the successor gets none
- repeat, conflict, second op, stale revision, confirm, halt (writes nothing), B3 both ways
- Q4 `<old>-N` with a taken `default-1`, and a cancelled op's name staying bound
- D8 start counters: held, refused, held, started, with attempt and work_attempt; complete after
- halt at retirement waits and writes nothing
- cancel before retirement (sage-2828); cancel after retirement and the thread-only send to the
successor (sage-2830); cancel after started
- B2 claim refusal and the claim after a cancel
- mos-1325 tester: own thread clears; clear, cancel and operation on a human thread refuse
`thread-not-owned` and write nothing; agents refuse `human-required`
- append-only triggers, `session_ops_one_open`, CHECKs and UNIQUEs below the broker
- `packages/cli/tests/session-clear.test.mjs` (3 tests, bwrap), the launcher half, with
`fixtures/clear-adapter.mjs` (session-adapter's turn plus /proc pids and pid 1's argv):
- a running session: one `session-clear-stop`, end, retired, successor launch in that order; the
successor starts fresh in its own workspace; N6 pid namespace check on the first turn
- held: capacity-full reported `held` and logged once, started when the side session idles out
- restart (sage-2830, Q2): a live survivor holds the clear at `drained` with no signal; its exit
lets the clear retire and start
Open, still to write before the code round:
- A `failed` clear (N4: a user's thread whose grant is revoked before retirement) and cancel after
`failed` refusing `session-clear-complete`.
- The launcher's `uncertain` path (a run that ignores SIGTERM past `stopTimeoutMs`). It needs an
adapter that traps TERM, and a short stopTimeoutMs.
- Mutants, at the run: the fence (`#sessionFence` in role.claim, thread.send, thread.open), the
drain at receive, the survivor wait in clearStop, CLEAR_HELD, and the `cancelled` filter in
`#threadRows`.
### N4 failed clear (round 1, not run)
Test "T4 N4: a user's thread whose grant is gone by retirement..." in packages/bus/tests/session-clear.test.mjs.
Finding for Duckworth: the `failed` path in sessionClearRetire (broker.mjs:1137, #ownerRefusal at broker.mjs:1191)
has no verb route in 271. A user can't call session.clear (USER_VERBS, broker.mjs:138, gate at broker.mjs:4452
gives access-refused), and the human or tester gets thread-not-owned on a user's thread. Tester and human threads
always pass #ownerRefusal while the business names them. So the test writes the op's requested and drained rows
directly, as the accept would, then runs exited and retire with the grant removed. The path becomes reachable
when a user verb for clear lands (264e or later). Question for review: keep the path as a guard, or drop it until then.
### Launcher uncertain path (round 1, not run)
Test "T4 D7: a drained run that outlives the stop timeout..." in packages/cli/tests/session-clear.test.mjs. The test
drives the ticks (tickMs one hour, stopTimeoutMs 1000) and SIGSTOPs the runner after the drain and before the tick
that sends the TERM, so the TERM stays pending. It checks one stop, one uncertain, no force, then SIGCONT and
retired plus started. Open: mutants (fence in claim/send/open, drain at receive, survivor wait, CLEAR_HELD,
the cancelled filter in #threadRows, the uncertain once-only branch at launcher.mjs:788).
### Mutants (round 1, defined, not run)
Each mutant is one edit on the 271 tree, and each kill is an assertion failure in a named test, not a timeout
(sage-2707). Lines are on gizmoduck/row271-r2 at e567f949c, after the rebase onto 0cbe2e9b3 and the B1
fix, which put one comment line above broker.mjs:1194 (launcher.mjs didn't move).
| # | File:line | Edit | Expected kill |
| --- | --- | --- | --- |
| M1 | broker.mjs:5158 | drop `#sessionFence(s.session)` in role.claim | bus T4 B2 (claim on a clearing session gives session-clearing) |
| M2 | broker.mjs:6146 | drop the fence in thread.send | bus T4 D7 held default (send during the clear refuses session-clearing) |
| M3 | broker.mjs:6134 | drop the fence in thread.open | bus T4 D7 held default (open refuses) |
| M4 | broker.mjs:6287 | drop the fence in session.clear | bus T4 D7 repeat/conflict: a second op refuses session-clearing. The session_ops trigger (one open clear) is a second fence and would refuse with its own text, so the kill is the code check, not "refused at all" (mutant-masked-by-second-fence) |
| M5 | broker.mjs:5599 | receive returns [] without writing drained | bus T4 D7 held default (stages after receive) |
| M6 | broker.mjs:1319 | drop the drain at endLaunch | bus T4 D7 run ends before its next receive |
| M7 | broker.mjs:6298 | always skip the drain at accept | bus T4 D7 unheld session |
| M8 | launcher.mjs:798 | drop the survivor wait | cli T4 D7 Q2 survivor (stage stays drained, no stop) |
| M9 | launcher.mjs:815 | CLEAR_HELD empty | cli T4 D8 held (start held, one log line) |
| M10 | broker.mjs:1209 | drop the message_cancels filter in #threadRows | cli T4 D7 running session (cancelled input would launch D again) or bus B1; the run decides which, both are named |
| M11 | launcher.mjs:788 | `stage === "drained"` becomes `true` | cli T4 D7 uncertain (one uncertain event); bus T4 D7 delivered-unread keeps its broker-side once |
| M12 | broker.mjs:1137 | `#ownerRefusal` returns null | bus T4 N4 failed clear |
| M13 | broker.mjs:1195 | drop the `'session.clear'` verb arg from `#ownerRefusal`'s `#granted` call (the r2 defect, duckworth-0201 B1) | bus T4 N4 live grant (refusal access-refused, state failed) |
duckworth-0201 B1 (early look on 911b90903): `#ownerRefusal` called `#granted(s, project, instance)`
against the signature `#granted(s, verb, project, instance)` at broker.mjs:919, so the project sat in
the verb slot and the instance in the project slot, and a user's thread with a live grant failed
retirement with access-refused. The N4 test revoked the grant first, so it could not tell the two
apart. The call now passes `'session.clear'`, which is not in VIEWER_VERBS, so the recheck is at
collaborator. The N4 setup is a shared helper, the revoked case is unchanged, and a second test keeps
the grant and expects retired plus the successor. M13 restores the defect. Both N4 tests are new 271
tests, so mos-1292 doesn't apply.
M10's kill is a guess until it runs; if neither test fails, I add a pending-count assertion and report it as a
first-run change under mos-1272.
## Rebase onto row 206: schema label 3m to 3n (sage-2968)
Row 206 landed 0cbe2e9b3 on refactor at 19:47Z with schema step 3l to 3m (thread_shares), and
origin/gizmoduck/row271 at 74b5e3638 also set 3m (Mos found the collision in mos-1335). The branch is
rebased on 0cbe2e9b3 as gizmoduck/row271-r2, a new name, so nothing is force-pushed. The rebase took
`git rebase --onto 0cbe2e9b3 2d0d407ba`, since the 269 commits under 271 landed as 269 r3. Row 271
becomes step 3m to 3n:
- `packages/bus/src/migrations/3m-3n.sql` is the old 271 `3l-3m.sql`, byte for byte. `3l-3m.sql` is
206's.
- store.mjs: `SCHEMA_VERSION = '3n'`, the 3m to 3n step after 206's, `session_ops` and
`message_cancels` stay last in `timedTables`, and `SCHEMA_SHA256` is the sha256 of the new
schema.sql (a2ba3a46...).
- schema.sql is refactor's with the 271 objects appended after 206's, unchanged.
- schema.test.mjs:526-529: 25 tables and 96 triggers, 206's 23 and 89 plus 2 and 7. `grep -c` in
schema.sql gives 25 `CREATE TABLE` and 97 `CREATE TRIGGER`; at 0cbe2e9b3 it gives 23 and 90 against
the pinned 89, so the extra hit is in the base file and the step adds 7.
- `packages/bus/tests/fixtures/schema-3m.sql` equals refactor's schema.sql at 0cbe2e9b3 (sha256
d5ca9bb9...).
- stack-release.sh:420 COUNTED and stack-release.test.mjs:861 LISTED: both tables after 206's
`thread_shares`, at the end of the list as before.
New tests in packages/bus/tests/session-clear.test.mjs, written like 206's in thread-shares.test.mjs:
- "T4 schema: a 3m store migrates to 3n": a 3m store from the fixture, with a row in each thread table
and a share, migrates. Both tables are new and empty, every other row stays, the migrated
sqlite_master equals a fresh store's, both tables refuse update, delete and replace, the one-open
trigger refuses a second open clear, and a cancel naming no message refuses on its foreign key.
- "T4 schema: the 3m engine refuses a 3n store": the 3m engine refuses with and without
migrateSchema and leaves the store as it was, and it opens a fresh 3m store.
### Pre-existing test changes on the rebase
The mos-1315 kinds, one label up:
- Current label `'3m'` becomes `'3n'`: account-migration.test.mjs:98, :148, :167 and rewrite source
:160; gitea-migration.test.mjs:102, :128, :146 and :139; grant-migration.test.mjs:107, :132, :150
and :143; chief-of-staff.test.mjs:641 and :617; comment-migration.test.mjs:36; name-migration :68;
spec-migration :66; usage-migration :71; thread-migration :104 and :52; sessions.test.mjs:377,
:450 and :433; thread-shares.test.mjs:406, :459 and the engine3l rewrite source :442.
- The offline command's ready line, `bus schema 3m ready` becomes `3n`: comment-migration.test.mjs:112,
thread-migration.test.mjs:164. My first build missed these two (`3l ready` then), so the first run
would have shown them red.
- The future step in comment-migration.test.mjs:78-79 moves to 3n to 3o, :86 expects `'3o'`, and the
:82 file list gains `3m-3n.sql`.
- Engine copies: account- :90, gitea- :80 and grant-migration.test.mjs :83 copy `3m-3n.sql` after
206's `3l-3m.sql`.
Changes outside the mos-1315 list, the same kind 206 made for thread_shares. Mos approved all three
under mos-1292 in mos-1336 (19:58Z). Conditions: neither name list grows past these two names without
Mos, and a gate red in these files outside the listed lines goes to Mos:
- `later3l` in account- :21-22, gitea- :38-39 and grant-migration.test.mjs :24-25 gains
`session_ops` and `message_cancels`, with a comment line. These tests compare sqlite_master before
and after older steps, and the 3m to 3n objects are new there just as thread_shares is. My first
build missed this too.
- `rows()` in gitea- :43 and grant-migration.test.mjs :45 leaves out both tables, as it leaves out
thread_shares.
- thread-shares.test.mjs:410-414: 206's 3l migration test now ends at 3n, so after it asserts
thread_shares empty it asserts session_ops and message_cancels empty and drops them before the
whole-table compare. Nothing it checked is removed.
A grep for `'3m'` in these files afterwards has no hit. session-clear.test.mjs's 3m engine is new. This is a rebase,
so the next run reruns every touched file whole.
## Run limits (mos-1275, sage-2991)
The gate on e567f949c (sage-2991). Each node --test file and suite runs under `timeout -k 10 LIMIT`, and
each scripts/test-*.sh under at least 3x its longest recorded run. The records are from
agents/gizmoduck/work/queue-269/BUILD.md "Run limits", plus row 269's r3 gate.
| Run | Longest recorded | LIMIT |
| --- | --- | --- |
| stack-release.test alone | 575 s (264a r12 survey) | 1800 s |
| each other touched file alone | 15 s (thread-launch.test) | 300 s |
| bus suite | 23 s | 900 s |
| seat suite | none recorded | 600 s |
| cli suite | 591 s (row 281 gate) | 1800 s |
| each scripts/test-*.sh | 42 s (test-foundation) | 900 s |
| each mutant run (one session-clear file whole) | none recorded | 300 s |
A timeout with no failed test is a hang and goes to Sage. A timeout in the shell phase goes to Mos
unplaced (mos-1275). A first red in a new 271 test is placed and reported after (mos-1272). A second
red, a red in an old test outside the mos-1336 lines, or a fix that needs a product change outside the
manifest or a looser assertion stops the run and goes to Mos and Sage (sage-2991). Mutants run only on
a green base.
## Gate on e8af389e6 (sage-2991)
The code on e8af389e6 is the same as on e567f949c. The only difference is the BUILD.md "Run limits"
section. The run went from 20:31:19Z to 20:56:32Z (1513 s wall), on base 0cbe2e9b3 with origin/refactor
at f2735bb6a, whose drift touches only docs/plans/QUEUE.md and queue.json. The logs are in
/mnt/storage/scratch/gizmo-271-out/gate/.
- The 18 touched test files, each run whole, all pass with 0 fail and 0 skip. Among them, bus
session-clear 19/19, cli session-clear 4/4, stack-release.test 218/218, thread-shares 16/16 and seat
compactions 20/20. Phase wall 596 s.
- Suites: bus 873/873 (24 s), seat 192/192 (6 s), cli 873/873 (588 s).
- scripts/test-*.sh: all 10 pass. auth 15, conductor 17, config 31, discord 81, extension-package 18,
foundation 44, land-work 86, queue 156 node tests plus 28, release 14, task 91. The skips are
test-queue's verify/render --check (this worktree isn't the canonical root) and test-task's live
provider cases (row 244). test-task has no Docker skip.
- The tree was clean after the tests and after each mutant restore. No node process was left with its
cwd in the worktree.
- First-run reds: none, so nothing was placed under mos-1272.
Each mutant ran with bus and cli session-clear.test whole. Every kill is an AssertionError in the
test this section named, never a timeout:
| # | Killed by |
| --- | --- |
| M1 | bus T4 B2 and T4 D7 held default |
| M2 | bus T4 D7 held default and T4 D7 delivered-unread |
| M3 | bus T4 D7 held default |
| M4 | bus T4 D7 repeat/conflict: `storage-refused` where `session-clearing` was expected, so the code check failed and the trigger was the only fence left. Also T4 D7 held default (`session-retired` expected, got null) |
| M5 | bus T4 D7 held default and delivered-unread; cli T4 D7 uncertain, D7 D8 N6 running session, and D7 Q2 survivor |
| M6 | bus T4 D7 run ends before its next receive |
| M7 | bus T4 D7 unheld session (stages `['requested']` where `['requested','drained']` was expected), and 7 later bus tests, 5 of them on `not-drained` or `session-revision-stale` refusals that follow from the missing drain; cli T4 D8 capacity-full |
| M8 | cli T4 D7 Q2 survivor |
| M9 | cli T4 D8 capacity-full held |
| M10 | bus T4 D7 B1 (cancelled inputs never reach the successor). The cli running-session test passes, so no pending-count assertion was added |
| M11 | cli T4 D7 uncertain |
| M12 | bus T4 N4 grant gone |
| M13 | bus T4 N4 grant live |
Open: the row's gate names tests T4 and T6 (queue row 271). This build maps T4. Of T6, these are
covered: clear under a kill or restart (cli D7 Q2 survivor, D7 uncertain), grant revocation (bus N4),
limits (cli D8 capacity-full held) and a halt at retirement (bus D7 N4 halt). These are not: a Q198
hold, a halt between reservation, spawn and bind, and a manager configured but unavailable. That goes
to Sage before the in-review move.
## Round 3: T6 (sage-3001, duckworth-0209, duckworth-0210)
Sage ruled the three T6 items left open above. Written with no runs; the r3 gate runs on a CLEAR.
- Halt between reservation, spawn and bind: covered by citation. session-launch.test.mjs:372 (the
halt fence, C2) halts once after check() and once inside its recheck, and neither attempt spawns.
A successor goes through the same path: clearStart at launcher.mjs:824 calls `check()` and
`start()` unchanged, so the fence that test pins is the successor's fence too.
- Q198 hold: binds row 257 (Morgana). Row 271 has no Q198 code to test: 257 hasn't landed, and
packages/*/src holds no pressure or Q198 code on this branch or on origin/refactor f2735bb6a.
Carry for 257 (duckworth-0210): 257 either puts its hold code in CLEAR_HELD (launcher.mjs:815) or
rules that a Q198 hold counts as a refusal, and either way it adds a clear-path test.
- Manager configured but unavailable: a new cli test, "T4 T6: a bus unavailable at the exit proof
and then at retirement" (packages/cli/tests/session-clear.test.mjs). The test's host op rejects
with `broker-exited`, the code host.mjs:303 gives when the broker exits before it replies. It is
down first at `sessionClearExited`, then only at `sessionClearRetire`. While it is down the test
expects no retired or failed event, no launch but D's, nothing in the session registry (the
successor takes no slot) and D still the thread's only and default session. The stage holds at
drained, then at exited. The failure is logged once in all, and no `thread tick:` line appears.
Once the bus answers, the clear retires and the successor starts, in the same event order as the
running-session test.
Reading of "capacity release": the launcher counts capacity from its registry of running sessions
(`limitHit`, launcher.mjs:403). D's run really ends, so its slot is free on the host either way;
what an unproven stop must not do is hand that slot to the successor. The test pins that.
| # | Where | Mutant | Killed by |
| --- | --- | --- | --- |
| M14 | launcher.mjs:755-759 | drop clearPass's per-clear try/catch, so a failed op leaves the pass and tick() | cli T4 T6 bus unavailable: the first `lines()` assertion. The clear's line is never logged; the ticker's catch logs `thread tick:` instead |
The harness the gate applies, M1-M14 with their exact anchors, is committed beside this file as
mutants.py (duckworth-0212 C1). `check` counts each anchor; `apply <id>` edits the worktree.
r3 gate, on a CLEAR (sage-3001): the touched files run whole, the cli suite runs whole, and M14.
No src file changed in r3, so the full gate isn't due again. A first-run red in the new test is
placed under mos-1272 and reported after; a fix that needs a product change goes to Sage and Mos
before any edit.
## r3 gate on 9e18a9aaf (sage-3017)
Run 21:16:37Z to 21:27:04Z (627 s wall), TMPDIR /mnt/storage/scratch/tmp/gJmI, limits as in "Run
limits" above (300 s per file and per mutant run, 1800 s for the cli suite). origin/refactor was
f2735bb6a. No src file differs from e567f949c, so the full gate wasn't due (sage-3001).
| Run | Result | Wall |
| --- | --- | --- |
| cli session-clear.test whole | 5/5 pass | 12 s for both files |
| bus session-clear.test whole (r3 touched no other test; the bus half of the clear) | 19/19 pass | |
| cli suite whole | 874/874 pass, 0 skipped or todo | 585 s |
| M14, bus session-clear.test | 19/19 pass (M14 is launcher-only) | 30 s for both |
| M14, cli session-clear.test | 1 fail: the T6 test | |
- First-run reds: none. The new T6 test passed on its first run, so nothing was placed under mos-1272.
- M14 kill: an AssertionError at session-clear.test.mjs:365, the T6 test's first `lines()`
assertion, as named above. The ticker's catch logged `thread tick: broker exited before it
replied` once per tick and the clear's own line never appeared.
- The tree was clean after the tests and after the M14 restore; launcher.mjs matched HEAD. No node
process was left with its cwd in the worktree.
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
# Row 271 mutants M1-M14 (BUILD.md "Mutants", lines at e567f949c; M14 is r3's). `apply <id>` edits the
# worktree and prints the touched path; each edit asserts its anchor appears exactly once.
import sys
WT = "/mnt/storage/scratch/gizmo-270/"
B = "packages/bus/src/broker.mjs"
L = "packages/cli/src/launcher.mjs"
M = {
"M1": (B, " this.#sessionFence(s.session);\n if (this.#sessionHolder(s.session)?.op === 'claim') fail('thread-already-held');",
" if (this.#sessionHolder(s.session)?.op === 'claim') fail('thread-already-held');"),
"M2": (B, " // Row 271 (264d, D7 step 1): nor sent to, by name or as the default.\n this.#sessionFence(session);\n",
" // Row 271 (264d, D7 step 1): nor sent to, by name or as the default.\n"),
"M3": (B, " // Row 271 (264d, D7 step 1): a session being cleared, or a retired one, isn't opened.\n this.#sessionFence(session);\n",
" // Row 271 (264d, D7 step 1): a session being cleared, or a retired one, isn't opened.\n"),
"M4": (B, " this.#processingWork();\n this.#sessionFence(session);\n if (this.#revision(session) !== a.revision)",
" this.#processingWork();\n if (this.#revision(session) !== a.revision)"),
"M5": (B, " if (!this.#opRow(open.thread, open.op, 'drained')) this.#appendOp(open, 'drained', { run: s.run, generation: this.#runGeneration(s) });\n return [];",
" return [];"),
"M6": (B, " if (open?.stage === 'requested') this.#appendOp(open, 'drained', { run: session.run, generation: h.generation });\n", ""),
"M7": (B, " if (this.#sessionHolder(session)?.op !== 'claim') this.#appendOp(op, 'drained');",
" if (false && this.#sessionHolder(session)?.op !== 'claim') this.#appendOp(op, 'drained');"),
"M8": (L, " if (survivors.some((x) => x.business === b.id && x.run === c.run && liveRun(x))) return;\n", ""),
"M9": (L, 'const CLEAR_HELD = new Set(["capacity-full", "limit-reached"]);', 'const CLEAR_HELD = new Set([]);'),
"M10": (B, "\n AND NOT EXISTS(SELECT 1 FROM message_cancels x WHERE x.message=m.id)) AS pending,", ") AS pending,"),
"M11": (L, '} else if (stage === "drained" && now - clearStops', '} else if (true && now - clearStops'),
"M12": (B, " const refusal = this.#ownerRefusal(t);", " const refusal = null;"),
"M13": (B, "this.#granted({ ...s, grants: this.#userGrants(s) }, 'session.clear', t.project, t.instance)",
"this.#granted({ ...s, grants: this.#userGrants(s) }, t.project, t.instance)"),
"M14": (L, " try {\n await (start ? clearStart(c, legacyThreads) : clearStop(c, now));\n } catch (e) {\n clearLog(c.op, e.code ?? e.message, `clear ${c.op} (session ${c.session}): ${e.code ?? e.message}`);\n }\n",
" await (start ? clearStart(c, legacyThreads) : clearStop(c, now));\n"),
}
cmd = sys.argv[1]
if cmd == "list":
print(" ".join(M)); sys.exit(0)
if cmd == "check":
for k, (f, a, b) in M.items():
n = open(WT + f).read().count(a)
print(k, f, n)
sys.exit(0)
f, a, b = M[sys.argv[2]]
s = open(WT + f).read()
assert s.count(a) == 1, (sys.argv[2], s.count(a))
open(WT + f, "w").write(s.replace(a, b))
print(f)
+10
View File
@@ -1558,3 +1558,13 @@ are never rewritten or removed; corrections are new entries.
2026-10-22 | Megavolt (Claude Opus 5.5) | Q195 O2 W4 DOCX citation/access design review (#1799, sage-3061/3067) | four rounds: CHANGES 15e91538f, 019a0ea5b, 54246cb32; APPROVE 994c3f7fb (verdict 67f82a12d, unpushed pending CLEAR); M1 closed with no ruling; R4-N1 to W4b
2026-10-22 23:48Z | rocko | Q195 O2 W4 #1799 design approved | W4 #1799 design approved at 994c3f7fb (Megavolt 67f82a12d), 32 acceptance rows assigned to W4b; static docs/public-fixture inspection only, no implementation PASS. Q36 remains OPEN and adoption CLOSED. Sage's accepted implementation brief remains required. Row219 remains separately held pending your next direction.
2026-10-22T23:55:11Z | Sage (Opus 5.5, 1ef1e4f8) | Row 203 landing, O2 design landing, W4 docs landing | 203 LANDED a4da4e6df (Webby c30877, rev 3366), #1701 closed c30882, P19 269 c835. O2 design (mos-1360/1361) pushed 25c09f19f, blob 322ec50f matches fenton-0008. W4 #1799 APPROVE round 4 posted c30884; design 2af460ab7 and review bedde1716 land here. Queue revs 3360-3368 in 4ca270307. 270+271 r4 rebased by Gizmoduck onto 25c09f19f for new Duckworth rounds.
2026-10-23T00:05:06Z | Filbert (Opus 5.5, sage-3059/3064) | Q195 O2 S0 (#1798) static review of Fenton's fenton/q195-s0 85ae2be77 (security + CSP reports, receipts in s0-20261022) | CONCUR CONDITIONAL PASS, c30914; A1 M1 needs WOFF2 and summed-size tests, A2 F2/D7 misstate the ctrl+click gate, M2 retargets to DOCX save; N1-N4 non-blocking; D8 hash question stays with Mos; no queue row (mos-1361), no slot, harness not rerun
2026-10-23T00:52:58Z | Gyro (Codex gpt-6.1-sol, mos-1366/1367) | Vikunja571 Q235-Q237 O2 parent/W4 format-policy rewrite on gyro/editor-o2 | Canonical queue next gyro: nothing. Prepared P1-P6 and B1-B4 docs, baseline W4 import 1ac76a417; no product/build/browser/worker/private-file/queue/live action. Mos impact map fold precedes review/push handoff.
2026-10-23T01:37:07Z | Gyro (Codex gpt-6.1-sol, mos-1368/1372/1375) | Vikunja571 O2 parent/W4 revision complete for review | P1-P6/B1-B4 and A17-A41/Q1-Q16 folded; same-thread Case 2 lookups/support writes, human-set host-pushed off-host remote, Hermes/restore/counting/order/role gates. Static docs checks pass; old locator/projection mechanics retained, authority/access review reopened. No code, build/browser/worker run, private conversion, queue or live action. Direct Fenton/Mos/Sage handoff after push.
2026-10-23T01:41:59Z | Gyro (Codex gpt-6.1-sol, sage-3121) | 571 PRD d6 A1-A3 alignment | Both designs corrected and statically checked; local commit for Fenton in shared repo object store. HTTPS push held for Sage gap, independent review pending. No product, test-slot, queue or live change.
2026-10-23T01:49:08Z | Gyro (Codex gpt-6.1-sol, fenton-0012/mos-1379) | 571 B1/B2 and N1-N6 docs corrections | Real-helper denial gate and two off-by-default human review settings; static acceptance/brief/immutable-mechanics checks pass. Local candidate for Fenton via shared Git objects; no HTTPS push before Sage gap, no runtime or credential experiment.
2026-10-23T01:53:25Z | Gyro (Codex gpt-6.1-sol, fenton-0013) | 571 N7 chapter-order clarification | D10 and W7 explicitly cover mixed formats and stable chapter identity/position. Docs checks pass; local candidate sent for narrow re-check, push held for Sage gap. No runtime or live change.
2026-10-23T02:21:06Z | Sage (Opus 5.5, 1ef1e4f8) | Gap after 270+271 run 5: landings and MVP rows | 270+271 LANDED 7328da8ca (mos-1351), 571 landed 767bb62c7 plus N7, rows 293-299 added and committed 253346e3c, 270 request c30929 and Duckworth verdict c30930 posted, 219 rebase conflict ruled (rocko/row219 fd17203ed), 574 CLEAR to Gander 02:19Z.
2026-10-23T03:15:01Z | Filbert (Opus 5.5, fenton-0011) | Q195 O2 S0 (#1798) re-check of Fenton's amendments at 74ec32013 | A1, A2 and N1-N3 closed, c30916; corrected my WOFF2 overstatement (wuff bounds WOFF2 at 128 MiB and ratio 100). Re-added: the uncommitted line from 00:1xZ was lost from the working tree. Static only, no slot.
2026-10-23T03:15:01Z | Filbert (Opus 5.5, sage-3114) | row 293 (Vikunja 574, #1805) static review of gander/v574 7da382244, then the r2 rebase 6bfab273b and the run 1 stop diagnosis | Four argv insertions only, base hashes match, fixture audit holds; P1-P5 proof folds; run 1 red was the 120-byte tools.sock path (session.mjs:278), harness-only. Re-added: the earlier uncommitted line was lost from the working tree. Static only.
2026-10-23T03:15:01Z | Filbert (Opus 5.5, gander-0182) | row 293 round 1 verdict, manifest cdda41f5 at a9cae2bd6 (tested 6bfab273b) | APPROVE c30982, queue rev 3411 (filbert-293-r1-approve). Run 2 GREEN is Gander's receipt (153/202/905, 312 flagged commits, 0 maintenance), not rerun by me. N1 doc-only: lifecycle.test needs TMPDIR of about 50 bytes or less.
+12
View File
@@ -638,6 +638,18 @@ before row 192 refuses the key). The message says to remove the key first. So a
window re-pins to row 192 or later before it writes the key, and a rollback past
row 192 removes the key first.
Two more optional system config keys bound the host's compaction collections
(row 270, #1775, DECISIONS Q222). Only a human writes them, like `console`.
`compactionReadBytes` is the stream bytes one collection reads, a whole number
from 1048576 to 1073741824, default 67108864 (64 MiB). `compactionStreams` is the
stream files one collection opens, a whole number from 32 to 65536, default 1024.
The loader passes either on as written. The launcher takes the default for a
missing or bad value and logs one `system config:` line. A collection that hits
either bound counts as `not-collected`, and the next one reads on. A copy before
row 270 refuses both keys, and the release's config check doesn't catch that
(it runs the target's loader only for a `console` key). So any re-pin or
rollback plan whose OLD copy is before row 270 removes both keys first.
#### A target past c216f47db: the PM after the harness kind
From c216f47db on, the bus launches the PM only with a `harness` kind in the
File diff suppressed because it is too large. Load diff
+259 -569
View File
@@ -1,78 +1,107 @@
# Q195 O2 W4: committed DOCX citations and access
# Q195 O2 W4: committed DOCX citations and Case 2 access
Design for Gitea #1799, assignment sage-3060/mos-1360. Owner Rocko;
independent reviewer Megavolt. Normative parent: `e36bce352`,
`docs/plans/2026-10-21_q195-writing-editor.md`, blob
`322ec50f2669cffc0f8256d6beffdaccc4d8dba8`, D12, D24-D26, W4 and W4b.
This is a proposed implementation contract, not access enablement or a parser
security PASS. Reviewer owns the separate verdict file; the author does not
write an approval there.
Policy rewrite for Vikunja 571, mos-1366/1367/1368/1372/1375, Q234–Q237.
Normative parent: `docs/plans/2026-10-21_q195-writing-editor.md` in the same revision.
Original W4: Rocko/Megavolt, #1799, refactor 062646487, blob
`2af460ab772f8d204598dc8cb79b3c161f311623`. Gyro writes this assigned revision; Fenton
independently reviews it. The reviewer-owned verdict file is untouched.
Impact map: brain-redux d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`, stack pin
062646487. Sections 1/2/6/7 and A19/A20/A28 are reopened, plus part/view authorization
wording. Exact locator, package-validation and projection mechanics in sections 3–5 remain
from the reviewed baseline except their replaced per-document-scope wording.
## 1. Gates and compatibility
## 1. Confirmed format policy and approval gates
W4b implements this contract only after independent W4 APPROVE and Sage's
accepted implementation brief, S0 and its other parent gates. A release must
ship extractor, resolver, authorization and sharing together; no partial
agent read rollout. Missing server readiness refuses every O2 read, resolve,
share and flag. A human setting, pin or request field cannot assert readiness.
W5 owns flags, not chapter writes. No agent execute, edit, replace, save,
tracked-change or comment-writing operation is introduced.
Q235–Q237 P1–P6 and mos-1372 D1/D2 settle the design basis: canon and playground story
files become DOCX, retaining universe/book and source scope. Type-folder notes, agent files,
instructions, PRDs, character sheets, summaries and supporting documentation stay Markdown.
An unmarked-folder story is eligible; the human selects one story file at a time. New
stories start directly as DOCX in the human-selected universe/book or playground, without a
Markdown precursor, under the same production-opening gate as import.
Q36 / Vikunja 562 is OPEN. Production Markdown adoption remains closed under
mos-1358, independently of whether this design is approved. If Jason approves
superseded notices, a separate C2 reader/reply row must implement and pass
them before adoption; otherwise Mos records Jason's disposition before any
adoption brief proceeds. This document neither implements nor decides notices.
The October 27 release stays Markdown. Production import/new-story opening waits for W4b,
W7, the post-10-27 release, the approved Case 2 PRD/brief amendment, Hermes retirement
(#423 step 5) and configured off-host durability. The first real use on Melanie's books
also waits for Jason's scoped go and mos-1374's protected manual VM1012 backup before the
first real import, handled by its authorized human operator, not this design seat. All starts are human-only; `library.convert` has no role
grant or agent request path and stays a hard wait unless an approved PRD changes it.
Nothing in this document authorizes conversion, new private story creation or deployment.
Q234's exact frame CSP, mos-1363 and S0 M1–M14 remain unchanged implementation gates.
Existing C2 Markdown, its line-cite schema, library grants and supporting-doc
writer remain unchanged. An authorized old Markdown original is historical
source, never the current DOCX or a permission to its later edits. No DOCX
paragraph is called a line. Following an adopted source from a C2 read,
indexing it as Markdown, changing C2 replies or a synchronized Markdown
projection requires a separately approved Case 2 amendment through Mos/Jason.
W4b must test old C2 fixtures unchanged alongside the new discriminated DOCX
contract. It may migrate only new O2 records, with a named schema edge Sage
allocates; it must not reinterpret existing line citations in place.
mos-1372 D9 replaces the prior W4 O2-only-conversation, per-document-grant and per-send-share
requirements for Case 2 lookups. Agents read converted stories through the normal Case 2
lookups in any thread or room under the same live project/universe/source-scope checks as
Markdown canon/playground. Final accepted text follows that access. Under mos-1379,
editor.agentReadComments and editor.agentReadTrackedDeletions are human-only booleans,
both false by default. The former gates comment stories; the latter gates original/markup
views. This is two settings, not a role-level allowlist or per-document grant. They can then use existing supporting-Markdown write tools under
Q223. The agent never writes a story/DOCX/archive file. D9 is marked as a choice for Jason
in PRD 1.17 approval; it is design text, not an already approved role/policy expansion.
No code may rely on that changed requirement before the approved PRD and independent review.
## 2. Trust and document identity
Human import commits the verified DOCX to a private staging ref first and obtains its
off-host push acknowledgment. It then creates/pushes the source archive move+mark through
new human-only bus plumbing on the C2-02/C2-03b writer, not two single-path saves. Finally
one host publication protocol advances writing-published and the catalogue under the
publication/read lock. No staged ID is reader-visible before catalogue CAS. Before that
switch, agents read the source at its pinned commit; sync cannot advance it early. After
it, only the current DOCX is a chapter. Pre-push failure leaves source Markdown unmoved;
after archive push, failure is a visible resumable pending operation, never an assertion
that nothing moved, a rollback push or Markdown revival. Old bytes/history remain.
The host reader alone opens `<dataRoot>/writing/<business>/<project>/`.
Business, project and document are opaque IDs resolved through the host's
owned manifest, never request paths. Each document has a stable ID and one
canonical DOCX path; a rename does not change its identity. A copy/new import
gets a new document ID and no inherited grant, share, pin or cache authority.
Origin metadata is evidence only. Copying a source does not launder access.
The archive is in the source-repository folder set by a human in the library mapping.
There is no default; missing/invalid folder keeps conversion closed. It is recorded with
converted date, source commit/blob/path and replacement ID in the operation record. It is
read-only in the Console. Explicit `archived` scope returns only requested authorized
records, always marked and non-canon; default/current queries never opt into it. An old
source lookup returns a typed conversion record and routes an authorized DOCX read, never
stale Markdown or fake DOCX lines. Historical line cites still verify at commit/blob and
show converted/historical state; archive access alone is not current story authorization.
Pre-conversion text already in thread history is not rewritten/refreshed; its cites verify
against their original commit/blob and show historical/converted state, not the current DOCX.
A Markdown file recreated at a converted old path is unmapped, neither canon nor playground,
excluded from lookups and flagged to the human. A new Markdown story at a different path in
a converted book stays canon until import, flagged and offered for human import; no automatic
conversion. Existing playground scope rules remain. Clickable DOCX editor links stay; no
DOCX graph exists yet. A Connections graph and its tests wait for Jason's J-b answer.
A read snapshots one reachable committed manifest and its DOCX blob from the
same commit. It never combines HEAD's manifest with another commit's DOCX.
The reader accepts only server-resolved commits reachable from that document's
published history; arbitrary git objects and another document's same blob
are not selectable. Historical access also requires the document's current
live authorization. Removed history, missing blobs or missing extractor
revisions refuse; there is no worktree, export or engine-memory fallback.
The commit/blob field is a locator, not an authority token.
Every conversion runs the pinned extractor before archiving and refuses unsupported output.
An independently reviewed importer preserves every wiki token and front-matter key.
The v1 extractor does not accept OOXML hyperlinks: emit Markdown label/URL as literal text
with a fidelity-report entry for lost clickable behavior, never w:hyperlink/r:id. Bare URLs
stay literal; wiki links stay exact and host-clickable. Unsafe/unrepresentable URLs refuse.
This handled literal mode is explicitly confirmed before archive push; metadata
moves to the canonical manifest (type/name/appears_in/relationships feed the index). Links
resolve across Markdown/DOCX through owned aliases, not host paths. A lost link fails the
fidelity report. Plain links/front-matter values resolve or raise R4; unresolved chapter
embeds must not take the old silent attachment-drop path. Images/.canvas/.base remain attachments.
The project's sole publication ref is `refs/heads/writing-published`; only
the human writer advances it, atomically and without force updates. Each
commit contains the book manifest. For a document, its publication commits
are the first-parent commits on that ref that add or change its DOCX blob or
its canonical manifest entry (including deletion/reinstatement). The first
commit is a publication; a sibling-only save is not. `currentCommit` is the
latest such commit for this document at the captured ref tip. Version latest
selects that commit; a locator commit must be one of those publication commits,
not an arbitrary intervening project commit. Deleted document entries refuse
current reads, including history, until explicitly reinstated and granted.
Published history is exactly that document's reachable publication sequence.
A sibling save does not change its currentCommit, stale flag or cursor.
Staleness is still exact commit inequality within that document, even if a
human deliberately saves an earlier byte-identical blob as a new publication.
## 2. Document identity, order and freshness
The writer's successful commit publishes atomically before Saved. A latest
lookup begun after that receipt resolves that published commit, not an old
cache entry. A lookup already in progress remains internally on its captured
commit. Return the captured version explicitly; never label it latest after
observing a newer publish. Recheck live grants immediately before returning
content, not just before asynchronous extraction.
Host code alone opens `<dataRoot>/writing/<business>/<project>/`. Opaque document IDs resolve
through the canonical manifest, never caller paths. Conversion preserves book/chapter identity
and scope, binding the original path/name as an alias to the DOCX ID. New stories get a fresh
ID and the human-selected universe/book or playground. No format-based canon promotion.
The map tells authorized agents format, source scope, owned location and current version;
archived entries are available only in explicit archived requests. No host paths/credentials.
A lookup snapshots the reachable committed manifest and DOCX blob from one publication.
No worktree, draft, archive or engine-memory fallback. `refs/heads/writing-published` is the
canonical served ref. A document's publications are the first-parent commits adding or
changing its blob/manifest entry; sibling-only saves do not change its currentCommit.
History locators name only such reachable publications, with current project/universe checks.
Deleted entries refuse until explicitly reinstated by an authorized human. New local saves
publish before acknowledgment. A lookup starting after an acknowledged save returns that
save or a later one and names it. A lookup already in flight stays on its captured version;
never label an observed older snapshot latest. Recheck live access before emitting text.
D10's ordered chapter-ID manifest drives manuscript paging and next_chapter after conversion,
replacing row216's initial path-order rule (RULINGS.md:32–34). Before the first conversion,
seed the order from that existing rule; replace each source entry with its DOCX identity in
place. A human reorder is a manifest commit. next_chapter never names an archive. Pending
unconverted files retain their legacy identity/scope until their individual publication
switch; no file has two working formats and new stories start as DOCX. Book-null notes
stay out of manuscript; hybrid exclusions and source-universe boundaries still apply.
## 3. Exact wire schema, version 1
@@ -81,7 +110,7 @@ commits/blobs are lowercase 40-hex Git object IDs in the repository's current
SHA-1 object format. A future object format requires a new schema revision.
Digest values are lowercase 64-hex SHA-256. Integers are safe nonnegative
JSON integers. No floats, coercion, duplicate JSON keys or implicit defaults
except the explicitly listed request defaults below. These are proposed O2-only records.
except the explicitly listed request defaults below. These are proposed DOCX records for the Case 2 lookup/citation union.
A locator is exactly:
@@ -214,10 +243,10 @@ extractor supports them; never guess equivalent namespaces.
| Class | Exact part names | Story selector | Agent exposure |
| --- | --- | --- | --- |
| body | word/document.xml | main | final by default, explicit grant |
| footnote | word/footnotes.xml | w:footnote/@w:id | own story plus owning document grant |
| endnote | word/endnotes.xml | w:endnote/@w:id | own story plus owning document grant |
| comment | word/comments.xml | w:comment/@w:id | only explicit comment scope |
| body | word/document.xml | main | final by default; existing project/universe access |
| footnote | word/footnotes.xml | w:footnote/@w:id | own story; same project/universe access as its document |
| endnote | word/endnotes.xml | w:endnote/@w:id | own story; same project/universe access as its document |
| comment | word/comments.xml | w:comment/@w:id | explicit part request plus human comments setting; same project/universe access |
| layout | word/headerN.xml, word/footerN.xml (N positive decimal) | none | denied in v1 |
| supporting | separately declared non-story document | separate identity | C2 tools/scopes only; no DOCX scope inheritance |
| opaque | media, styles, numbering, theme, fonts, custom XML, embedded objects | none | no raw bytes through O2 reader |
@@ -288,8 +317,12 @@ kind paragraph-mark-insert or paragraph-mark-delete with its revision ID;
it never becomes a whole-paragraph deletion span. A mark-only insertion's
text stays in original; a mark-only deletion's text stays in final. Inline
run revisions remain independent. No view edits or accepts/rejects changes.
Original/markup and comments require separate scopes, so final body access
never reveals hidden deletion/comment text. Revision author/date metadata
Original/markup require editor.agentReadTrackedDeletions=true; comment stories require
editor.agentReadComments=true. Both are human-only and false by default (mos-1379); a comment
in original/markup needs both. Check them in addition to ordinary project/universe access,
refusing with editor-access-refused and no text when off. No role-level allowlist or
per-document grant is added. A final-body read stays available and never silently includes
deleted revisions/comments. Human editor review/save remains full-featured and unaffected. Revision author/date metadata
is omitted from agent responses in v1.
Closed story/run leaf table (all names in w namespace unless stated):
@@ -372,7 +405,8 @@ plain text, and tokens cannot cross paragraph/view boundaries.
## 6. Reading APIs, verification, paging and staleness
These are proposed typed tool schemas over the existing runner socket, not
native WordCraft MCP. Caller/session/business come from the bound broker.
native WordCraft MCP. The same reader backs Case 2 lookups in any authorized thread or
room under mos-1372 D9. Caller/session/business come from the bound broker.
Agents cannot override them using object fields.
- `editor_read_committed({project,document,version,textView,part,cursor,limit})`:
@@ -412,14 +446,14 @@ without progress.
Cursor is a host-authenticated opaque token binding principal, session,
project/document, commit/blob, extractor, view, part, next ordinal, the complete
authority revision vector defined in section 7 and expiration (5 minutes). It carries no plaintext content.
project/universe authorization and format-publication revision vector defined in section 7 and expiration (5 minutes). It carries no plaintext content.
Tampering, reuse by another session, wrong request parameters or a revoked
scope refuses. A latest cursor continues its captured snapshot with stale
metadata if a save intervenes; starting again with null cursor returns the
new publication. Cursor is not a capability and grants are rechecked every
page. Repeated ordinal ranges are stable within one committed projection.
Resolver steps: authorize the current principal tuple, live scopes and exact/historical share (there is no separate undefined historical-view scope); load
Resolver steps: authorize the current principal, live project/universe/source scope and publication membership; load
that commit's manifest/blob; validate package; run the exact pinned extractor;
select exact part/story/ordinal and mandatory XML-ID value; select code-point range;
require literal quote equality and SHA-256(UTF-8 quote) equality. Require both
@@ -431,14 +465,13 @@ an explicit old-version label. If current grant no longer allows it, even
old quote resolution refuses and the UI hides cached content.
Errors: `editor-context-unavailable` for missing readiness; `editor-access-refused`
for absent ownership/grant/share (same response for nonexistent or foreign IDs);
for absent live project/universe authorization (same response for nonexistent or foreign IDs);
`editor-document-unsupported` for parser limits or unsupported features after
authorization; `editor-version-unavailable` for authorized missing history or
extractor; `editor-citation-invalid` for locator/quote mismatch;
`editor-cursor-invalid` for invalid paging; `editor-range-too-large` for size.
The exact refusal envelope is `{ok:false,error:{code,message}}`; code is one
of those enum strings or editor-policy-conflict/editor-sharing-unsupported
(both 409, defined in section 7), and message is fixed server text for that code (no user
of those enum strings or editor-policy-conflict (409, stale catalogue/settings revision), and message is fixed server text for that code (no user
input). Both missing/foreign and ungranted IDs use the same code/message/status
403. Syntax errors before identity lookup use editor-citation-invalid, 400;
context unavailable is 503, cursor-invalid 400, version unavailable 409,
@@ -446,417 +479,142 @@ unsupported/range-too-large 422. Successful tools return `{ok:true,result}`
where result is the defined response; errors never include result/partial
rows. Return no source text, XML, path or untrusted parser error in refusals.
## 7. Authority data, dedicated sessions and sharing
### 7.1 Scope and principal records
### 6.1 Case 2 query and citation adapters
O2 context is permitted only in newly created **O2-dedicated conversations
and execution sessions**. Existing C2 threads/sessions cannot be converted,
attached to O2 context or resumed/forked into O2 mode; new IDs and a fresh
empty context are mandatory. There is no mixed-thread turn. The UI offers
"Open dedicated writing conversation", never silently changes an existing
chat's permissions. Existing C2-02 supporting-doc writes and C2-06 replies
retain their semantics. The narrowing below belongs only to this explicitly
opted-in O2 mode, closing review M1 without a Case 2 amendment. It changes
neither the Case 2 schema nor the meaning of its replies/line citations.
Keep `library_find`, `library_get`, `library_search`, `library_manuscript` and the universe
lookup. Host adapters join Markdown metadata/read results and the committed DOCX extractor
into one typed, scope-filtered index; only current identities enter normal lookups. A request
on a converted alias returns its conversion record and the authorized DOCX result. Every
answer names format, store/owned ID and captured commit. No loss of chapter search/manuscript
or supporting-doc workflow is accepted. Same project/universe/source-scope rules apply in
all threads/rooms; archive/draft scope never becomes canon because its format is DOCX.
Authorization principal is a tuple, not a seat's display name:
`{business,human,agentInstance,conversation,executionSession}`. human is the
stable authenticated identity that owns the O2 conversation; agentInstance
and executionSession come from its bound broker run. The request cannot
supply/replace them. All human readers and all agent lookups require live
R5 project/universe access plus the O2 grant below; role files grant the
operation only. The business owner is not an implicit reader of someone
else's private conversation. A viewer can read only their authorized human
view, never write a share/settings change or invoke an agent by sharing.
An owner/collaborator with current project write access may share within
their own readable O2 scope. Agent scope and sharing human scope intersect;
a human cannot share a part or view they lack.
DOCX pages use paragraph locators and opaque cursors, not invented line equivalents.
Retain section 6's 64-KiB/1..100 paragraph bounds (default20); human reply limits still apply.
A manuscript cursor binds ordered-manifest commit, the selected document publication vector,
principal/request/scope revisions and expiry. Resume only that snapshot with current access;
changed/foreign cursor refuses. next_chapter uses the next visible current manifest ID,
never an archive or a guessed sort of DOCX names. Index/cache keys include local publication
and extractor revisions; no minute-old Markdown refresh window is copied to DOCX.
O2 scopes live in the new append-only bus table writing_access_events, not
access.json or role-file wildcard changes. Proposed human-only verb
`editor.access.set({project,document,subject,scope,expectedRevision})` takes
subject exactly `{kind:"human"|"agent",id}`; agent id is the business's
bus agent **instance ID**, equal to principal.agentInstance, never a role
definition ID or a seat display name. scope is exactly
`{enabled,parts,views,history,mayShare}`. parts is a sorted unique nonempty
subset of body/footnote/endnote/comment (all stories within the selected
class); views is a sorted unique nonempty subset final/original/markup;
history and mayShare are booleans. mayShare must be false for agent subjects.
enabled=false revokes all access; even its otherwise populated scope cannot
authorize. No wildcards or implicit note/comment inheritance. A comment
requires parts including comment and the requested view. history=true permits
older publications subject to a matching share; history=false permits only
the current publication. The document is always explicit.
C2-06's checked cite union preserves legacy `{path,commit,lines,quote,as?}` for Markdown and
adds `{kind:"docx",locator,as?}` where locator is section3's exact range/selection. Never infer
a cite kind from a .docx suffix or parse DOCX as lines. The host checks project/universe,
source canon/playground/archive status, fetched provenance, quote and commit before message
storage. requireCitation and as:canon apply to both; historical converted/archived sources
cannot assert current canon. The host reads Mosaic-Agent trailers from the correct source
repository or writing-store publication, never trusts an agent author field. Console and
the shared reply renderer show format, locator/lines, version, staleness and converted state.
C2-18 Discord remains a separate ruled delivery task, not enabled by a shared renderer.
Only the existing business-owner human proof may write these policy grants,
through the existing trusted human CLI or authenticated write-enabled Console
transport, with the existing origin/session/proof checks. No new proof file,
caller or exemption. Not tester, agent, viewer or collaborator policy writes.
Sharing and scope are different: an authorized collaborator may send context
but cannot change grants. expectedRevision is the latest sequence for that
business/project/document/subject, zero when none. Compare, append and audit
are atomic; conflict returns editor-policy-conflict (409, fixed message).
Receipt is `{ok:true,result:{id,revision,previousRevision,scopeDigest}}`, where
revision is the appended seq, id is host-generated, and scopeDigest is SHA-256
of canonical scope JSON using section 3 serialization. Missing grant denies.
## 7. Existing Case 2 authority, remote durability and restore
Each access decision records an authority vector exactly
`{restoreEpoch,humanAccess,agentAccess,projectAccess,threadAccess,sharingPolicy,share}`.
humanAccess and agentAccess are the respective latest writing_access_events
seq values; projectAccess is the existing R5 access revision; threadAccess
is the latest O2 conversation membership-event seq; sharingPolicy is the
latest writing_policy_events seq; share is the exact writing_shares seq.
restoreEpoch is the independent restore-fence epoch in section 7.5.
Missing inputs deny rather than zero-default except initial policy explicitly
materialized by the human writer as the default. A cursor binds every value;
any change invalidates it. Each new lookup reevaluates rights, including a
revocation between asynchronous extraction and response emission.
### 7.1 Access and supporting writes (reopened M1)
### 7.2 One human per context; settings and chip requests
Reuse existing bound business/project/universe access, live holder/role and source scope.
No private O2 conversation ID, per-document access table or send/share token gates a Case 2
lookup. Any existing authorized thread/room may use the checked reader and normal library
tools. No arbitrary native WordCraft MCP, execute, open/save or raw filesystem/git tool is
added. The host store is never mounted in a worker. Rendered/extracted content travels over
the ordinary authorized Case 2 tool/message path and existing transcript/run-record policy;
the withdrawn private-only transcript/output rule cannot forbid that documented workflow.
Grant loss blocks later lookups through existing checks; do not promise erasure of earlier
delivered text or rewrite immutable run evidence. This is the D9 choice for PRD approval.
An O2 conversation has exactly one owner human and one agent instance. No
second human joins it in v1: a thread-share attempt refuses
editor-sharing-unsupported (409), rather than borrowing the sender's grants
for someone else. A second human starts a separate O2 conversation, supplies
their own grant/share and gets no previous content. This is a new O2 mode,
not a restriction on existing C2 sharing. Human and agent must still have
matching business/project/universe rights. Read-only previews cannot send.
The existing `library_doc_write`/`library_doc_delete` still require library.write plus the
live supporting-repo Gitea grant and supportingPath before git. They operate only on human-
marked supporting Markdown, with the existing size/base-CAS/seat-author/push/notice/revert
rules. An agent can read a DOCX story and update its character sheet in the same authorized
thread. There is no agent write to DOCX, original story, archive or conversion manifest.
The archive prefix wins over supporting globs; conflicts refuse mapping. Format/status/type
notes in the six creative contracts replace stale 'every item cites lines' wording with
free chat plus optional checked line/DOCX citations under requireCitation. No per-document
export grant or O2-only session is a hidden prerequisite.
Human-only `editor.sharing.set({conversation,mode,expectedRevision})` is
available only to that authenticated owner with current write access over
the same trusted write transports. mode is off/visible-chip-per-send/
conversation-current. It atomically appends writing_policy_events and returns
`{ok:true,result:{id,revision,previousRevision,mode}}`. Default is the explicit
visible-chip-per-send policy on new conversation creation. Selecting off
immediately invalidates **all** prior shares, reads, resolves and cursors for
that conversation, not only new sends. Selecting another mode later does
not revive shares from the earlier policy revision. No pin changes a mode.
The cursor authority revision is the server's current project grant, universe/mapping/scope
revision, the two human review-settings revisions and format-publication generation for
the bound caller/session. It contains no
sharing human or writing_access_events/writing_shares sequence. Check the review settings before every part/view/citation/search/index/context attachment,
not only on first open. No request boolean can enable them. Recheck before every page,
resolution, index hit and message insertion. Scope revocation and format changes invalidate
cached authorization; metadata for missing/foreign IDs uses the same refusal as before.
In visible-chip-per-send the human sends exactly
`{conversation,message,expectedPolicyRevision,chip}` via the human send path.
message is human-authored free-chat text; W4b's bounded human API handles it,
never an agent-supplied impersonation. chip is null or one of:
### 7.2 Selection convenience and ordinary messages
- `{kind:"range",document,baseCommit,selection}` where selection is one
docx-range/docx-selection locator, with its business/project/document/
commit equal to the server-owned conversation/document and baseCommit.
- `{kind:"document",document,baseCommit,parts,views,history}` selecting a
whole-document **exact publication**, with explicit part/view lists and
history boolean constrained by both grants.
Keep Q233's human-only setting, default a visible removable chip on each send. A chip may
attach only a verified committed locator/version, never unsaved selection text. It helps the
writer point at a passage; it neither grants nor limits the agent's ordinary project/universe
lookup rights and does not freeze a later latest lookup to that older commit. The sent cite
remains immutable historical evidence. Multi-human/thread/room behavior follows existing Case 2
permission checks, not a new one-human conversation restriction. Saved-context/pin records
remain on the bus through their reviewed schema, with no share-token authority layer.
Both chips require a server-issued human editor selection token as an
additional required `selectionToken` key. It binds authenticated login,
conversation, document, baseCommit, saved editor model revision, and either
exact locator or whole-document scope digest, expires in 2 minutes and is
single-use. The human editor adapter creates it only after exact mapping to
the committed projection. The request supplies no draft text or engine path
for mapping; mismatched/edited selection fails locally and server-side.
A stale base or document change before send refuses, requiring a fresh
visible chip; an old citation remains resolvable through its already-sent
share while authorized. Null chip sends no editor context. Before readiness,
the chip says unavailable and sends no document ID, title, path or selection.
### 7.3 Host-only off-host remote (replaces the ban)
A range share permits only editor_resolve_citation for that exact locator
(or a range wholly contained within it with newly verified quote/digest).
It always refuses editor_read_committed, since rows would expose surrounding
text. A document exact share allows paging only its named publication,
parts/views; it never grants future saves. No implicit body-to-note/comment
or final-to-markup expansion occurs.
`editor.remoteURL` is a human-only setting, default unset. No same-host bare/filesystem/loopback
remote is the default or qualifies as off-host durability. The human selects an off-stack-host
private destination (for example private Gitea) and separately configures host-only auth.
Until #431 corrects the real seat helper's shared jason.woltje credential route, the remote
must be on a host/account that principal and all seat credentials cannot reach; private
visibility alone proves nothing. Prefer a separate off-host instance. An existing-instance
owner/org qualifies only after denial through the actual helper credential path proves no
jason.woltje access. W3b tests that real helper route and seat identities against an authorized
empty fixture remote, without printing/logging credential values; no dummy-token substitute.
Only the host process pushes. No seat token can list/fetch/clone/fork/push the writing repo,
no seat gets its credential, and no raw Gitea grant or store mount is created. Validate the
owned remote, TLS/host policy and off-host storage identity; reject URL userinfo, unexpected
remotes/object alternates and non-fast-forward updates. Allow host pushes only to refs/heads/writing-published and
refs/heads/import-staging/<operation-id>. A refused candidate retains a private non-reader
staging ref and journal status; after publication the staging ref stays as an audit link.
No automatic deletion or force-update of staging refs. No silent fallback or force push.
conversation-current is an explicit human policy with a persistent visible
indicator and a required whole-document chip. The same chip fields bind
baseCommit and scope, but the share record has versionMode=latest: baseCommit
is the acknowledged publication at issuance, not a future-version ceiling.
The next authorized latest lookup after Saved follows currentCommit within
the granted part/view/history bounds. This exception to exact-version sharing
exists only in this explicit mode. Range chips are refused in this mode,
not silently upgraded. Pins and earlier exact shares never become latest.
Every writing-store commit pushes there. Agents read the local publication immediately;
receipts/UI distinguish local commit and remote pending/failed/synced. Missing remote blocks
production opening. Before source archive push, the staged DOCX must have remote acknowledgment.
A failed later push keeps local commits, queues bounded host retry/readback and shows failure;
it never rolls back the local story. Source archives push through their separate human
library writer. The configured retry deadline is human-only (default30s, 1s..300s).
Remote/provisioning changes require the normal owner authority, never this design execution.
The remote exception is for story git history/manifest; generic stack copies/releases are
not story backups and contain no host credentials. Normal Case 2 output/notes follow the
existing project's approved storage/sharing policy; the former blanket no-backup/no-remote
rule is withdrawn. PRD 1.17 explicitly presents this D7/C4 change to Jason.
Latest shares expire on explicit policy revocation, owner grant loss, O2
conversation close or **execution-session end/clear**. This means the new
O2 execution session, not the Console login, bus connection or a C2 thread
session. Relaunch/clear requires a fresh chip; a dropped socket/login does
not silently widen or prolong one. Exact shares may remain verifiable across
execution sessions through their same human conversation while live policy,
history permission and grants hold, but never supply replay context to a
new agent session without a new share. Their scope is citation verification
only after the issuing execution session ends. No cross-human context reuse.
### 7.4 Conversion events, rollback --restore and irreversible recovery
### 7.3 Stored share schema
W7 owns `writing_conversion_events`: a STRICT append-only table with seq INTEGER PRIMARY KEY,
at TEXT, business/project/document/operation_id/stage TEXT, record_ref/record_digest TEXT;
All fields except seq are NOT NULL; operation_id+stage is unique, no update/delete. The private host journal/record contains source
commit/blob/path, archive path/commit, DOCX commit and conversion date; the row is metadata
and never prose/credentials. Records are flushed before their bus references. Table rows
join stack-release COUNTED and T46 exactly once, so post-backup conversion events contribute
to --events and the existing restore approval/counting behavior. No new private conversation,
per-document grant or per-send share tables are created for D9.
The immutable writing_shares payload is exactly
`{version,id,restoreEpoch,business,project,document,conversation,executionSession,message,
human,agentInstance,mode,versionMode,baseCommit,blob,extractor,scope,selection,
authority,policyRevision,issuedAt}`. version=1; IDs use section 3 grammar;
mode is the policy at issuance; versionMode is exact/latest; baseCommit/blob
are the acknowledged snapshot and extractor is its exact pinned revision.
scope is `{parts,views,history}` as above; selection is the verified locator
for a range, otherwise null. authority is the complete revision vector at
issuance; its share field equals this row's seq (assigned atomically).
restoreEpoch must equal authority.restoreEpoch and the live independent
fence. issuedAt is canonical UTC millisecond ISO timestamp. policyRevision equals
authority.sharingPolicy. message names a new writing_messages row, not a C2
message. A unique `(conversation,message,document)` constraint and operation
idempotency key prevent duplicate grants on retry. No user-provided human,
agentInstance, authority, issuedAt, blob or share id is accepted.
A scope decrease/revocation denies even historical shares; an unrelated new
policy revision also invalidates prior policy-bound shares. A human who
needs them again explicitly reshares. Missing session/share is no context.
### 7.4 Dedicated storage and every output surface
Every turn receiving O2 context runs with the new O2-only allowlist of
read/resolve/flag plus its dedicated conversation reply. No generic file,
shell, git, supporting-doc writer or arbitrary outgoing tool is available.
This narrows only new O2 sessions, never C2 turns. All agent output from such
a turn is conservatively O2-derived; it stays in the dedicated conversation
with the union of the document provenance/scopes. No semantic quote detector
or agent self-labelling supplies this boundary. Human text and titles in that
conversation use the same dedicated storage, so a human pasting an O2 passage
cannot accidentally route it into an old renderer. O2 replies never enter
C2-06 processing. An O2 flag is metadata stored by W5, not a story edit.
| Existing surface | Required O2 behavior |
| --- | --- |
| threads/<business>/<thread>/session and claude-projects | no O2 content, prompt, model transcript, title or derived reply |
| threads/<business>/<thread>/sessions/<session>/session and claude-projects | same; O2 mode never binds these native stores |
| session-workspaces/<business>/<instance>/<session> | no O2 text or exports; no store mount |
| launches/<business>/<run> including bundle, turns, runner.log and session | opaque IDs/status only, never O2 prompts/output; host transient O2 adapter instead |
| bus events, messages, message_threads and existing sessions rows | no O2 text/titles/quotes; only fixed readiness/status audit with opaque IDs if needed |
| Console legacy thread view | no O2 rows; dedicated writing view reauthorizes every read |
| CLI thread, talk, follow and raw-session tools | no O2 content; unsupported mode refuses, not generic rendering |
| Discord journal.mjs/context.mjs and control-board Discord delivery | no O2 conversation subscriptions/context/output; fixed unavailable notice only |
| <dataRoot>/runs write-once evidence | never O2 text, prompts, titles, excerpts, stdout/stderr or derived output; only opaque run status |
| bus-host/sessions.json, launch logs and activity projections | IDs/liveness only, no document or conversation title |
Host-only `<dataRoot>/writing-context/` (0700 owner, no generic mount/remote)
holds the immutable dedicated message/share objects defined below, transient
O2 adapter material and any unavoidable encrypted native transcript. Object
files are regular 0600 owner files, no symlink/hardlink aliases. This is an
O2-classified content store; it is never included in the bus release backup,
post-attempt store, stack-copy, rehearse input or generic backup. Revocation
changes permission at read time, never rewrites a write-once run record. It is keyed to O2 session, not an alias/symlink into seat stores.
Release readiness requires an independently tested adapter that prevents
plaintext native persistence/logging outside this boundary, including crash
and timeout paths. If either Pi or Claude cannot meet it, that harness's O2
tools stay off; do not expose the session then attempt post-hoc redaction of
write-once run records. Readiness is per adapter. No O2 text enters immutable
legacy evidence, even briefly. Stored dedicated replies are authorized afresh
at read time; live grant loss hides cached UI content and prevents prompt
replay/fork. Revocation cannot erase already delivered knowledge and the UI
must not promise that it can.
O2-derived exports, extraction indexes, caches, previews, source copies and
git history retain document classification outside generic roots and C2
library mappings. O2 repositories have **no git remotes**. No push, mirror,
fork, plaintext backup or remote replication of an O2 repository is allowed,
including through human credentials; no seat Gitea account can discover,
list, fork or clone one. Backup transport is out of scope and disabled, not
an implied exception. Stack-copy/release copies source code and extractor
artifacts only, never writing/, writing-context/, writing-control/, O2 content
objects, exports or repository object directories. The separate release bus
backup retains metadata rows only, under the private-store rules below;
it is not a source copy or permission to read context. No raw O2 Gitea grant may be minted.
Validate absence of remotes at store creation, before save/read and activation;
a new remote or object alternate disables O2, rather than trusting a read-only
mount. Symlinks, hardlinks, alternate worktrees and parent mounts must not
expose it to generic tools. Native engine APIs remain human-only.
### 7.5 Named migration and rollback contract
W4b adds the following new STRICT append-only bus tables (seq INTEGER primary
key, at TEXT canonical timestamp, id TEXT unique, all other fields NOT NULL
unless stated). Each row also has restore_epoch TEXT NOT NULL, equal to the
independent current fence epoch at write time. Every update/delete/replace
refuses; latest seq **within that epoch** is state.
JSON columns contain the exact canonical schema named above, not arbitrary
unvalidated JSON. No existing C2 messages/events/sessions column changes.
| Table | Additional columns and key |
| --- | --- |
| writing_access_events | business TEXT, project TEXT, document TEXT, subject_kind TEXT (human/agent), subject_id TEXT, by_human TEXT, previous_revision INTEGER, scope_json TEXT, operation_id TEXT UNIQUE; latest per document/subject |
| writing_conversations | business TEXT, project TEXT, owner_human TEXT, agent_instance TEXT; one creation row per id, private O2 identity only |
| writing_conversation_events | conversation TEXT FK, op TEXT (close), by_human TEXT; latest membership/close revision, creation seq is initial threadAccess |
| writing_execution_sessions | conversation TEXT FK, execution_session TEXT UNIQUE, adapter_revision TEXT; no native transcript path |
| writing_session_events | execution_session TEXT FK, op TEXT (end/clear), by_principal TEXT; terminal event invalidates latest shares |
| writing_policy_events | conversation TEXT FK, mode TEXT, by_human TEXT, previous_revision INTEGER, operation_id TEXT UNIQUE |
| writing_messages | conversation TEXT FK, execution_session TEXT FK nullable for human-only messages, from_kind TEXT (human/agent), from_id TEXT, content_ref TEXT, content_digest TEXT, operation_id TEXT UNIQUE |
| writing_shares | conversation TEXT FK, message TEXT FK, document TEXT, payload_ref TEXT, payload_digest TEXT, operation_id TEXT UNIQUE; UNIQUE(conversation,message,document) |
Each table also has business/project columns if not otherwise listed; FK and
application checks require agreement, not just ID existence. The canonical message content object is exactly `{version:1,business,project,row,nonce,body,provenance}`:
row is the owning writing_messages row ID; nonce is fresh cryptographically
random 32-byte lowercase hex, stored only inside the private object, not its
metadata backup. body is
the complete human/agent message string; provenance is an array of `{document,share,commit,blob,extractor}`,
sorted by document/share; every entry requires live scopes. The share content
object is exactly `{version:1,business,project,row,nonce,payload}`, where row
is its writing_shares row ID, nonce has the same fresh/private rule, and
payload is the full section 7.3 record including any quoted selection.
Neither message bodies nor share payloads/quotes enter bus.sqlite, WAL,
events, SQL error logs or backup manifests. Human messages with no O2
attachment have provenance=[] but use the same private content store.
content_ref/payload_ref are opaque 64-hex object names, not paths; their
corresponding digest equals SHA-256 of the exact canonical UTF-8 object.
Objects reside at writing-context/objects/<business>/<row>/<digest>.json;
row/business/project must match both reference metadata and the authorized
caller. No cross-business or cross-row deduplication; retention is per owning
row and cannot delete another business's object. The private random nonce
prevents a metadata digest from confirming a guessed short body. No remote or
release-backup copy. Write/flush the immutable object before committing its
bus reference. A failed transaction leaves only an inaccessible orphan,
never a visible partial message/share; a missing or mismatched object refuses.
Object reads always require current authority, not knowledge of a digest.
No
existing renderer queries these new tables; generic bus recordEvent and
recordTask cannot write their rows. Access/policy/shares use only the trusted
handlers and atomic transactions. Grant seq values are table-specific, with
kind-qualified revision vector fields; never compare unrelated seq counters.
Sage allocates the schema edge; migration creates these empty tables and
triggers, with no backfill/conversion of C2 citations, messages or raw sessions.
Production adoption stays refused. The writer registers new documents without
creating grants; the owner explicitly sets both human/agent document scopes.
O2 session creation and its default policy are one atomic operation behind
owner write proof; receipt `{id,executionSession,policyRevision}` contains no
content. No legacy thread/session can be attached by guessed ID.
Old release rollback must not render O2 data. The new store schema version
normally makes old binaries refuse opening it, but security does not depend
on that: even a restored pre-W4b database reader can see only its unchanged
legacy tables with no O2 content. O2 messages/share/provenance remain in new
tables, never a tagged body in existing messages. Activating a pre-W4b copy
disables O2 context, never converts dedicated conversations to C2 or exports
transcripts for compatibility. O2 repositories/stores remain outside copies.
A rollback probe uses recognizable secret fixture text in O2-only content objects,
then invokes old Console/CLI/Discord/raw-session renderers: none may emit it,
its title, quote or prompt. No live rollback is part of acceptance testing.
#### Release backup, restore counting and independent revocation fence
The existing release behavior must be explicitly accounted for: at this base
`scripts/stack-release.sh:474-493` copies bus.sqlite into
`<dataRoot>/backups/stack/<ACT>/bus/`; restore_store at :498ff moves current
bus/ to `<dataRoot>/bus.post-attempt-<stamp><suffix>/`. rehearse reads backup
stores, and release never prunes them. These paths remain **O2-classified
metadata stores** even though content/quotes are outside SQLite. Directories
are 0700 owner and files (including SQLite/WAL/SHM if present) 0600 owner,
with ownership, symlink/hardlink and generic mount checks. No remote, mirror,
seat token access or generic export. rehearse may read metadata for validation
only and cannot load content objects, emit quotes or restore readiness.
Release does not silently delete evidence. A separate explicit human/ops
retention action may prune a stopped backup/post-attempt tree only after
checking it is not the selected recovery set or needed restore evidence,
recording its IDs/digests and a prune receipt. No automatic age-based prune,
no editing of retained metadata, no pruning runs/ under this policy. Until
that separately reviewed action exists, keep these private local metadata
trees indefinitely; it grants no content access. writing-context objects
have a distinct reviewed retention process and are never backed up by release.
W4b owns the release guard changes, not a follow-up: all eight writing_*
tables above join COUNTED in scripts/stack-release.sh:420 and the T46 table
classification in packages/cli/tests/stack-release.test.mjs:913. They are
non-event writers, not classified as ignorable tables or double-counted as
generic events. Jason's existing exact --events N confirmation remains
required when the combined post-backup row count is nonzero. A restore plan
also compares writing row identities with the proposed source store; equal
aggregate counts cannot conceal a dropped revocation replaced by another row.
A schema with any unclassified writing_* table refuses restore readiness.
An **independent host-private fence** lives outside bus/ and every backup:
`<dataRoot>/writing-control/restore-fence.json`, directory 0700, regular file
0600, no links/mounts/remote/source-copy/rehearse access. Its exact schema is
`{version:1,epoch,restoreOperation,droppedRowsDigest,confirmedBusinesses}`.
epoch is a host-generated opaque ID; restoreOperation is null before the
first restore, otherwise a host-generated opaque operation ID; droppedRowsDigest
is SHA-256 of the canonical sorted dropped `(table,id)` list (empty allowed);
confirmedBusinesses is a sorted unique array of authenticated business IDs.
Only the trusted release controller and existing owner-proof handler write
it by durable atomic replacement; agents/testers/request paths cannot.
It is not a human-editable readiness setting: verified release readiness is
still required, and this file is an additional independent deny fence.
**First creation:** the compatible release controller durably creates the
fence as part of W4b's first schema migration, before committing those empty
tables or enabling any handler. It uses a fresh epoch, restoreOperation=null,
droppedRowsDigest=SHA-256 of the canonical UTF-8 empty JSON array `[]`, and
confirmedBusinesses=[]. Every initial writing row uses that epoch. A normal
never-restored store does **not** require editor.restore.confirm: the
confirmation gate applies only when restoreOperation is non-null. Initial
live grants/shares and verified artifact readiness are still mandatory.
Initialization is allowed only when the pre-migration schema has no writing_*
tables/history and there is no existing fence. If a crash leaves a durable
initial fence but the schema transaction uncommitted, migration resumes with
that same epoch, not another initial state. If writing tables/history exist
and the fence is missing/malformed, fail closed; never repair by creating a
new initial fence. Creating an initial fence is not a way around a previous
restore or revocation. W4b acceptance exercises both migration crash orders.
Before **any** store swap/restore (not just one with nonzero counts), the
current compatible controller durably creates a fresh epoch, records dropped
writing rows and clears confirmedBusinesses. If fencing fails, restore
refuses before changing bus/. A crash anywhere keeps O2 denied. Old snapshot
rows have old restore_epoch; they cannot authorize even if their grant,
policy or share says enabled. Automatic failure recovery and rehearse-driven
restore use the same fence. Normal code rollback without store restore
keeps the fence and leaves O2 off on incompatible code. A pre-W4b controller
is not allowed to restore an O2-enabled data root: the active controller's
capability preflight refuses it; operations must use the compatible restore
controller even when the target runtime is old.
After restore, the verified W4b runtime remains unavailable for O2 in every
unconfirmed business. New human-only
`editor.restore.confirm({expectedEpoch})`, under the business owner's existing
trusted human proof, acknowledges that business in the independent fence
and returns `{ok:true,result:{business,epoch,confirmed:true}}`. It restores
**no old grant/share/policy/conversation**. Fresh current-epoch grants,
a fresh dedicated conversation/default policy and fresh human chips must
be created explicitly. Past rows may be inspected by an authorized recovery
operator as metadata, but never used as current authority or prompt replay.
Every read/resolve/cursor/share checks matching restore_epoch and, when
restoreOperation is non-null, that business's explicit confirmation; old object bytes remaining on disk are not capabilities.
Thus even owner confirmation cannot revive a previously revoked old share.
Missing/malformed fence state denies O2. This current-epoch file does not
claim to detect a privileged manual rollback of both fence and database;
the supported restore protocol never restores the fence from a backup and
always rotates it first. A same-uid operator bypassing that protocol is not
a boundary this file can prove. --events N approves a
store operation only and is never a substitute for this recovery protocol.
Acceptance seeds a secret in a content object and a share, takes a backup,
revokes access/sharing, then restores the backup with explicit row-loss
approval: the old share stays refused before and after owner confirmation;
a new grant/conversation/chip is needed. Sweep bus.sqlite, WAL/SHM, every
backups/stack and bus.post-attempt-* tree, rehearse output and release logs:
no secret/title/quote/body marker may appear. Only the explicitly named
private writing-context content object may contain it. Verify COUNTED/T46
covers each table and simulate failure before/after each fence/store step.
The XML/ZIP parser safety ceilings and absence-of-remote checks apply to
human store ingestion too where needed to prevent path escape and remote
replication; a projection-unsupported rich feature alone does not reject a
valid human save. Parser refusal codes have a fixed human-readable reason
category, never raw XML or a misleading empty context. Fields/hyperlinks/
text boxes may make real chapters agent-unreadable in v1; the UI explicitly
says this and never claims an empty successful projection.
Every extractor revision referenced by retained publication history or shares
is retained as a verified immutable host artifact. An activation missing one
refuses O2 readiness (no silent citation downgrade), including rollback to a
W4b-era release that drops a revision. A pre-W4b release can run with O2 wholly
off and cannot read those records. Retiring revisions requires a separately
reviewed retention/prune operation for all dependent O2 history/shares; not
an automatic stack-copy cleanup. Artifact caches are not authorization caches.
Three recovery cases are required: (1) crash after durable DOCX staging but before archive
push, (2) crash after archive push before publication, and (3) rollback --restore from a
pre-conversion bus backup while archive commits and O2 documents remain. The source is pinned
until final publication in cases1/2; after archive push state is visibly pending, never
'original untouched'. Case3 drops conversion rows only through the counted/approved restore
path. The durable host conversion journal/format registry is not restored from that older bus
backup. A compatible controller compares journal, both git histories, remote acknowledgment
and restored metadata before lookups/writes resume; it reconstructs missing metadata through
new recovery events without reviving Markdown or any old access grant. If reconciliation
cannot prove a single current identity/version, refuse that project's lookups and surface the
pending operation to the human. Do not replay chapter text into an old index or hide event loss.
Incompatible old binaries cannot activate converted projects through the supported controller;
raw privileged rollback tampering is outside the guarantee. Never rewrite git/run history.
Hermes retirement (#423 step5) is a production-opening prerequisite, with old story-write and
store-read access removed before any conversion/new-story operation. W7 checks its retirement
receipt and host capability preflight; no design seat retires services or accesses Melanie's
host. Independent tests use fixtures only. Preserve the existing stack grant/restore behavior;
the previous unimplemented W4 private-share restore-fence/table design is withdrawn with M1,
not backfilled into legacy rows. Conversion's durable registry is not an authorization token.
## 8. Acceptance matrix and release evidence
Each row needs a named fixture/test and a mutant or bypass probe in W4b's
packet. Independent fixtures construct OOXML directly and recompute Unicode
slices/digests outside the production extractor. Security checks fail closed;
no fixture-only readiness flag may exist in production APIs.
These are required future fixture gates, not implementation results.
| ID | Evidence required | Negative control / bypass |
| --- | --- | --- |
@@ -865,117 +623,49 @@ no fixture-only readiness flag may exist in production APIs.
| A03 | XML ID absent/duplicate and ordinal behavior | missing/mismatched paraId cannot silently choose another node |
| A04 | Emoji, combining characters, tabs, breaks and split runs | UTF-16 offsets, normalization and whitespace collapse fail |
| A05 | Literal split-run wiki forms retained | no path/URL fetch; malformed/cross-paragraph tokens stay text |
| A06 | Final/original/markup independently verified with moves/deletions | final-only grant cannot read deletion/comment; no document rewrite |
| A06 | Final/original/markup independently verified with moves/deletions; review settings off by default | final read succeeds; original/markup or comment request/resolve refuses until matching human setting, both needed for original comment; no document rewrite |
| A07 | Body/note/endnote/comment exact ownership | orphan/dangling/cross-document references and duplicate IDs refuse |
| A08 | Package allowlist, aliases, CRC, XML/ZIP limits | external rel, macro, traversal, symlink, entity, ZIP bomb and timeout refuse |
| A09 | Commit snapshot and publication ordering | next latest after Saved gets new commit; no worktree/draft fallback |
| A10 | Historical resolve stale label | old quote stays old; removed grant/history refuses without text |
| A11 | Pagination complete/deterministic and bounded | tampered/foreign/revoked cursor and oversized row refuse |
| A12 | Cross-user/business/project/universe denial | guessed IDs, same blob elsewhere and copied document grant nothing |
| A13 | Visible range chip exact boundaries and default off before gate | no selection/title/path metadata before readiness; draft mapping refused |
| A14 | Every lookup intersects live grant and share | pin, old send, revoked scope and range-to-whole-document escalation fail |
| A15 | Whole-document latest share explicit; save rechecked | exact share cannot reveal future edits; persistent policy visible |
| A16 | Generic file/search/git/export/index paths denied | source copy, symlink, hardlink, alternate object root and new O2 copy probes |
| A17 | Raw session, fork/resume and transcript boundaries | revoked context cannot replay via raw tool or harness history |
| A18 | Agent tools exactly read/resolve/flag after gates | native MCP, execute, save, replacement and story comment writes refuse |
| A19 | Existing C2 Markdown reads/line cites unchanged | no DOCX-as-lines conversion or redirect following adoption |
| A20 | Adoption gate before/after readiness and Q36 disposition | absent/revoked server readiness and required notice readiness refuse |
| A12 | Cross-user/business/project/universe denial | foreign project/universe IDs, same blob elsewhere and guessed unpublished objects grant nothing |
| A13 | Selection chip points only at committed text; ordinary lookup authority independent | draft mapping refuses; missing chip does not deny an authorized lookup |
| A14 | Normal project/universe/source-scope checks in every thread/room | pin, guessed ID, foreign universe and revoked holder cannot grant access |
| A15 | Latest after acknowledged save; explicit historical cite stays immutable | chip/per-send snapshot cannot force stale latest results |
| A16 | Host-only store and remote credentials; checked reader routes only | no worker store mount, arbitrary git/engine access or seat remote token; actual seat helper credential denied, not just a dummy token |
| A17 | Existing Case 2 message/transcript policy | no O2-only/raw-session backdoor that skips existing thread/project checks; no false erasure promise |
| A18 | Reader/resolve/flag plus existing Case 2 lookups/supporting writer after gates | native MCP, execute, save, replacement and story comment writes refuse |
| A19 | Unconverted/support Markdown unchanged; converted path returns record and DOCX | no stale archive as chapter or DOCX-as-lines; historical line cite verifies by old commit/blob |
| A20 | Conversion/new story gates W4b/W7/PRD/post-10-27/Hermes/owner go | agent start, missing readiness, early release, fixture flag and missing first-real-book go refuse |
| A21 | Human full-feature save remains separate | unsupported projection yields no agent context, never lossy human save |
| A22 | Scope revocation during extraction | final pre-emission check prevents async stale-authority leak |
| A23 | Sibling chapter save | currentCommit/stale/cursor for untouched document remain unchanged |
| A24 | Full page and oversized paragraph | end before overflow; first oversized row refuses without text; no unreachable subrange claim |
| A25 | No O2 git remote or release copy | seat Gitea list/fork/clone fails; configured remote/object alternate disables O2; copies exclude stores |
| A26 | Every section 7.4 store/renderer | secret marker absent in each named legacy store, raw session, CLI/Discord/Console view and runs/ |
| A27 | Pre-W4b rollback reader | new O2 rows never rendered through legacy tables; missing retained extractor disables O2 |
| A28 | Dedicated mode vs C2 | mixed attachment/resume/fork denied; unchanged C2 supporting writes/replies fixtures pass |
| A29 | Two humans/viewer/grant revisions | viewer cannot share; second human cannot join or reuse context; all cursor authority fields checked |
| A25 | Every read/write/tool surface uses existing Case 2 isolation | same document ID across business/universe or replayed cursor refuses |
| A26 | Host-only off-host remote and per-save push | unset/local-only/helper-readable/unexpected remote refuses opening; real helper denial tested; push outage visible, local next lookup still fresh |
| A27 | Conversion restore accounting and durable journal reconciliation | pre-conversion --restore loses counted row but cannot revive Markdown; no lost-event concealment |
| A28 | Reopened M1 supports same-thread DOCX-read then supporting-note write | any room/project-authorized caller works; no per-document/share token; story/archive write refuses |
| A29 | Readiness is reviewed runtime/extractor plus approved PRD, not caller fields | no request/go/setting can assert release or Hermes retirement |
| A30 | Canonical schemas and complete selections | unknown keys, inconsistent ranges, xmlId null mismatch and noncanonical story IDs refuse |
| A31 | Closed leaf/revision table | every leaf/nested rule; mark-only deletion keeps text in final, mark-only insertion keeps text in original; markup mark spans zero-width; Word -1 separator/0 continuation-separator and older 0/1 reserved layouts validate, references to them refuse |
| A32 | Initial migration and revocation/restore | initial null-operation fence needs no restore confirmation but no grant is implicit; non-null restore fence needs confirmation and fresh authority; all tables counted/classified; old share stays refused; crash/backup-marker sweeps |
| A32 | Existing grant/restore controls and conversion metadata COUNTED | restore cannot revive grant, current Markdown or uncounted conversion event |
| A33 | Staging/archive/publish journal and operation readback | crash/sync at every boundary; pinned source until publish; post-push failure pending, not undone |
| A34 | Manifest order, typed index/links and format map | next_chapter never archive; draft scope retained; cross-universe/hybrid exclusions and R4/R5/R6 |
| A35 | Per-file fidelity, source history, human controls and missing-folder refusal | all constructs/wiki/front matter preserved; no agent conversion; new DOCX has no Markdown precursor |
Implementation acceptance includes writing, bus, harness, business, WebUI,
seat mount/import closure and stack-copy/release fixtures; explicit production
adoption refusal/lift tests; independent parser/policy/tool review; G1 and
Sage's allocated schema edge. Evidence must name skips and refuse enablement
when an acceptance guard was not exercised. W4's review alone lifts nothing.
Implementation acceptance includes exact schemas/parser fixtures, live project/universe
revocation probes, same-thread supporting writes, remote denial/failure, counted-event restore
cases, Hermes gate, pinned-copy/sandbox closure and G1. All skips are named; an unexercised
guard blocks enablement. Old O2-only/private-share acceptance wording is replaced, not claimed
passed. No test, conversion or runtime experiment ran for this rewrite.
## 9. Handoff and unresolved decisions
## 9. Reopened review and handoff
Megavolt reviews this separate document and records a verdict in
`docs/reviews/2026-10-22_q195-docx-citations.md`. Revisions repeat direct
commit/blob handoff to Megavolt and Sage. Q36 remains with Jason through Mos;
no adoption, C2 reader/reply notice change or schema reinterpretation ships
on this document. W4b's brief must allocate ownership for every surface in
A16/A17 and section 7.4 before code: an unowned enforcement path is a blocker, not a deferred security
note. No live files, editor source imports, parser execution or tests were
used to write this design.
## Round 2 changes against review 08fcb8c1c
B1: exact selection wrapper, canonical paragraph identity, wiki/anchor schema,
chip/share and refusal records; ordinal-primary D25 refinement explicit.
B2: closed leaves and nested/paragraph-mark revision rules. B3: per-document
publication sequence and sibling-save acceptance. B4/B5: concrete append-only
authority, human proof, exact/latest share policy, revision vector and
single-human dedicated session lifetime. B6: complete-row paging; oversized
paragraph unsupported, no circular subrange API. B7: no remotes/backup push,
seat token and copy probes. B8: named native stores, renderers and immutable
run evidence, with O2-only storage/adapter readiness. B9: tables/columns,
rollback separation and extractor retention. M1: dedicated O2 conversations
only; mixed C2 context refused, no C2 requirement change. N1-N7 addressed in
canonical manifest, bound-business check, ratio/default wording, UI unavailable
reason, release-derived readiness and new A23-A31 acceptance rows. These are
proposed definitions, not passing implementation tests. Q36/adoption unchanged.
## Round 3 changes against review 0d627a2b3
R2-B1: paragraph-mark revisions preserve all run text; markup has a zero-width
mark span, never a fabricated whole-paragraph deletion. Reserved note leaves
are allowed only in reserved stories; whole-part structural validation is
explicit. A31 covers both mark-only cases and reserved notes.
R2-B2: bodies/quoted share payloads move out of SQLite into private content
objects; backup/post-attempt metadata paths are named with access/retention
rules. Every writing_* table is COUNTED/T46-classified. A non-restored deny
fence changes epoch before any store swap; owner confirmation cannot revive
old-epoch grants/shares. A32 tests revocation/restore and secret-marker sweeps.
R2-N1: agent grant subject means agentInstance. R2-N2: structural leaves have
one table. This remains design only; no code, restore, parser run or test.
## Round 4 changes against review 9fb999abf
R3-B1: reserved-note classification is by type with any canonical signed ID,
not sign; normal notes require nonnegative IDs, and references to reserved
notes refuse. A31 covers both actual Word layouts. R3-B2: first migration
creates the durable initial epoch before schema commit; null restoreOperation
needs no recovery confirmation, a restored epoch does. Missing established
fences never reinitialize. A32 covers first enable and post-restore states.
R3-N1: business/row/private random nonce are part of each content object and
its path, so no cross-business deduplication or guessed-body digest oracle.
R3-N2: removed the unsupported claim to detect privileged manual rollback
of the fence itself. R3-N3: restore_store anchor corrected to :498.
### Public Word fixture confirmation (not an extractor test)
Inspected only public Microsoft-maintained test packages in memory using
Python ZIP/XML readers, not the O2 parser or a live/user document. No binary
was imported into this repository. Source:
[OfficeDev/Open-Xml-PowerTools FootNote.docx](https://github.com/OfficeDev/Open-Xml-PowerTools/blob/a9187c5ec4199106562ec91c5ffd9f355251253b/NewDocxDocuments/FootNote.docx),
14,214 bytes; DOCX SHA-256
`40e2de78f0f70d8acf3db734df13f107c92986f1a40865cdf9772a8b55ab5e20`.
Its app properties identify Microsoft Office Word, version 15.0000.
footnotes.xml has separator=-1, continuationSeparator=0, normal note=1;
endnotes.xml has separator=-1 and continuationSeparator=0.
footnotes.xml SHA-256
`1da08d9c0a4a1af2706c035dfb176defd179686f81418134b796d49649e850a0`;
endnotes.xml SHA-256
`f83e7f1c9f91b44db26b147b22ca615a13488bc6a8084febb392ee00a6ca4f5e`.
This confirms the reviewer's -1/0 example in a Word-identified fixture.
An older independent Microsoft SDK fixture confirms why IDs cannot define
the class:
[Open-XML-SDK ForTestCase/footnote.docx](https://github.com/dotnet/Open-XML-SDK/blob/431ab05cf160248cc3885a4a766026d4f8243792/test/DocumentFormat.OpenXml.Tests.Assets/assets/TestDataStorage/v2FxTestFiles/ForTestCase/footnote.docx),
69,706 bytes, SHA-256
`26dc62a5d00e2c3c1df60e0d5bf1cac43b142a090ea595b9ad11ce73e3dab268`.
It identifies Microsoft Office Word 12.0000 and uses reserved IDs 0/1 in
both parts, with normal footnotes 2/3. No claim is made that the whole
packages pass v1's otherwise deliberately restrictive projection rules.
Fenton reviews this policy rewrite against mos-1368's A17–A41/Q1–Q16 and mos-1372 D1–D9,
including reopened M1 and A19/A20/A28, host-only remote and C5 restoration. Megavolt's old
APPROVE stays historical at the original blob; no author writes its verdict file. The
locator/projection facts remain source-backed, but the new authority/tool/runtime contract
requires its own independent approval and the Case 2 PRD approval, not a silent addendum.
Owner sends both design blobs/commit to Fenton, Mos and Sage. No code, private document,
conversion, source build/browser test, queue entry or live change is authorized here.
+182
View File
@@ -0,0 +1,182 @@
# MVP readiness rows: FED R6, R4, R7, the GitHub grant method, the Q228 grant scope, and 574/573
Sage, 2026-10-23. Mos filed these under mos-1371 (readiness at-risk) and accepted the owners, reviewers and
slot order in mos-1372, from sage-3112. Row ids come from the queue CLI at add. Every row below has gate owner Sage.
Slot order per sage-3112, with R6 first among the FED rows because it blocks 208, R7 and R8.
The federation rows follow Gizmoduck's design `agents/gizmoduck/work/federation/DESIGN.md` at b93f098f0 on
`gizmoduck/federation-design` (r4.11, sha256 1bcee8df0a3a8628a6646535b756c936ca6d9e7bcfa7e1dd2bf1d3f01715c8e6),
sections 3.1, 3.7 and 7, under Vikunja P19 #414. The R3 brief (`docs/plans/2026-10-22_federation-r3.md`) is the
template.
Common boundaries for every row here: no change to `~/.mosaic-dev`, `~/.config/mosaic-dev`, the live units, any
live access.json or any live credential. Tests use scratch roots under `/mnt/storage/scratch/tmp`, never `/tmp`.
Single test files run freely, and suites and mutants need Sage's slot. Every old-test change follows mos-1272.
## Brief: FED R6, Console granted-user sign-in
Issue #1800.
Owner Launchpad (built row 192). Reviewer Duckworth. Parents done: 192 (#412 phase 1), R1 (201), R2, R3 (274).
Row 208 gets an after on this row.
What to build, in `packages/webui/src/` and `packages/cli/src/access.mjs`:
1. Granted-user sign-in. The Console verifies the OIDC token itself (Q175) and matches `(provider, sub)` to a
live access.json grant through R1's loader. An ungranted `sub` is refused.
2. A `user-cli` transport per session, R3's path-bound `packages/bus/src/user-cli.mjs`. The Console never
speaks for a granted user through `human-cli`.
3. The grant is re-read on every request. A user revoked mid-session gets 401 on the next call.
4. Owner-only controls are hidden from a granted user. The bus still refuses them (R3), and the mutant proves
the bus refusal stands on its own.
5. The notice on the user's thread page, row 193's sentence word for word (B9, N21): "The business owner sees
your thread's agent's decisions, inbox entries, claims, tasks and project files, but not your messages."
6. Peer links and the hub label (design 3.1 D1). The Console shows which instance it is from
`federation.self` and links each `federation.peers` entry by label and origin. A peer link is a plain link.
7. The F8 peers wiring. `accessContextFor` at `packages/cli/src/access.mjs:62` returns `peers: []`, so an
access.json that names a peer can't validate. It must pass the configured peer ids from
`system.federation.peers`.
Tests, webui tests on Sage's fake OP plus the cli access tests: design section 7's R6 gate item by item
(granted user signs in, ungranted `sub` refused, revoked mid-session gets 401, the owner-verb mutant, the
notice equals row 193's sentence exactly), one test that the hub label and peer links render from config,
and one cli test that a configured peer id validates and an unconfigured one refuses. Mutants: the UI check
removed (owner verb still refused by the bus), the per-request re-read skipped, the notice text changed by
one word, `peers` back to `[]`.
Gate: Duckworth approves the manifest. The webui, cli and bus suites and every `scripts/test-*.sh` are green on
Sage's gate, and the mutants are killed. The live provider client and the hub proxy route are R8's.
## Brief: FED R4, revocation
Issue #1801.
Owner Morgana (built R3, 205 and 275). Reviewer Dewey. Parents done: R3 (274), R5 (202), 193a (204). Design
section 7 also names row 193's viewer level (mos-1019, N17). If 193 hasn't landed that level when R4 starts,
R4 builds on 193's candidate, and the downgrade test and its mutant run on that base (Q177). Row 275's
"after FED R4" note becomes an after on this row.
What to build, in `packages/bus/src/broker.mjs` and `packages/cli/src/launcher.mjs`, per design section 3.7:
1. A broker timer every 30 s that re-reads access.json and re-evaluates expiry.
2. `access.reload`, a human-only verb. R5's `mosaic access grant` and `revoke` call it after the write.
3. On a loss, one transaction ends the claims of every `user:<id>` thread no live collaborator grant covers
(`#threadHolders`, `#claimEnd`). The run's next bus call is refused.
4. `threadsState` marks such a thread `revoked: true`, computed from the file on each call. A downgrade to
viewer counts as a loss (`covers` with `level: 'collaborator'`).
5. The launcher never starts a revoked thread, SIGTERMs a running one at once without waiting for
`threadIdle`, and its launch check refuses a revoked thread.
6. R5's help text keeps the provider-removal order (access.json first).
Tests, bus and launcher tests, each with a mutant (design section 7): a hand edit removing the grant ends the
run within one timer period, an expiry with no request ends it, a pending message followed by a launcher
restart starts no run, a running revoked thread gets SIGTERM, a downgrade to viewer counts as a loss, and
another user's runs stay. Timer tests drive the clock or the timer callback. No test sleeps 30 s.
Gate: Dewey approves the manifest. The bus, cli and business suites and every `scripts/test-*.sh` are green on
Sage's gate, and the mutants are killed.
## Brief: FED R7, scratch two-instance acceptance
Issue #1802.
Owner Quackerjack (built R5 and R2). Reviewer Filbert. After R4 and R6, and row 190 (several instances on one
uid). Briefed now and claimable once R4 and R6 both have candidates. It builds on their combined candidate.
What to build: one script test, under `scripts/` with its harness in `packages/*/tests/` as row 190's tests are.
It sets up two configs, two data roots, two Consoles and one fake OP. The Consoles start outside agent ancestry
in a transient user unit, as the user proof requires. They never use the live stack's units, ports or
sockets, and the script removes its units on exit (trap).
Test, design section 7's R7 gate: Jason's test identity, granted collaborator on one hub project, signs in to
the hub, makes a thread, sends to the granted instance, gets the instance's reply in the thread, is refused
on another project and on board reads, then is revoked, is refused, and has the run ended. The factory shows
nothing of the hub. Mutants: the grant's project list widened to `*`, the revocation's claim end skipped.
Gate: Filbert approves the manifest. The script test and every `scripts/test-*.sh` are green on Sage's gate, and
no unit, port or socket of the script's outlives the run. R8 is the live window and stays row 209.
## Brief: the GitHub grant method (Bushroot's half of #436b)
Issue #1803.
Owner Bushroot. Reviewer Dewey. After row 198, so Bushroot isn't on two rows. Q188 item 3: on Approve,
protected stack code creates the grant on GitHub as it does on Gitea. The Gitea half is row 236 (landed
bd5f11ded) under #436b design r5 (`agents/megavolt/work/436b/DESIGN.md` at 1be535aca).
Step 1, before any code: Q-B3. Bushroot answers design section 10's shared items S1-S6 in the packet, and
megavolt-0036 is waiting on that answer. Where GitHub doesn't fit (S4, because App installation tokens expire
within an hour and may not fit "mint once and store"), say so and propose the per-method S4. The rest stays
shared. Dewey and Megavolt read the answer before the build starts. A change to a shared item that 236 already
built goes to Sage first.
Step 2, the build: a `github` method behind the same grant body (S1), admin slot (S2), `grant.call` and
`grant.result` records (S3), executor steps (S4 or its per-method form), revoke order (S5) and capability
floor (S6). Scopes are fixed by code per method.
Tests: a fake GitHub API only. Mirror 236's test and mutant list for the GitHub method, plus the S4 expiry case
if the method refreshes tokens. No call reaches github.com. Live use waits for Jason's admin credential step,
as it did for Gitea, and no agent sets the admin slot.
Gate: Dewey approves the manifest. The touched suites and every `scripts/test-*.sh` are green on Sage's gate, and
the mutants are killed.
## Brief: the chief-of-staff grant scope (Q228)
Issue #1804.
Owner Megavolt (built 236 and 281). Reviewer Dewey. After row 252, because the inbox Approve path is the one
this row lets in-scope grants skip.
What to build, per DECISIONS Q228 (Jason, "Q31: no"):
1. A grant-scope setting for the chief of staff that only a human changes (Q222). A missing or bad value falls
back to the default, which is an empty scope, and every grant then needs Jason's Approve as before.
2. A chief-of-staff grant request that is inside the scope is issued by the protected grant code without a
per-grant Approve. A request outside it goes to the inbox as today.
3. Every grant is logged and revocable, in-scope ones included, with the same records 236 writes.
4. Jason's Approve stays for the chief of staff's enrollment and for any change to the scope.
5. Gosalyn's Case 2 write path keeps its grant step. Only the approver changes.
Tests: an agent or tester caller can't change the scope, a bad value falls back to the default, an in-scope
request issues with no inbox item, an out-of-scope one waits for Approve, a scope change needs Approve, an
in-scope grant is logged and revocable. Mutants: the scope check skipped, the fallback skipped, the human
check on the setting removed.
Gate: Dewey approves the manifest. The touched suites and every `scripts/test-*.sh` are green on Sage's gate, and
the mutants are killed. The live scope value is Jason's to set.
## Brief: 574, maintenance-free test fixture commits (Vikunja 574)
Issue #1805.
Owner Gander. Reviewer Filbert. Gate owner Sage. Ruled in mos-1369 A4 and mos-1370 under mos-1272. Slot: ahead
of the next row after 270+271 (A5).
Fixture git helpers commit through `gitEnv()`, which has no maintenance config. On git 2.55.0 a commit spawns a
detached `git maintenance run --auto` that can write and delete `objects/maintenance.lock` after commit
returns, while a test fingerprints the whole root, `.git` included. This turned 270+271 run 4 red at
`packages/seat/tests/checkout.test.mjs:239`.
Change: only `-c maintenance.auto=false -c gc.auto=0` added to the fixture git helpers, with no assertion
change, and the fingerprints still covering `.git`. Candidate `gander/v574` 7da382244 (source 65e919e5c) on
062646487, four files: `packages/seat/tests/checkout.test.mjs`, `packages/seat/tests/working.test.mjs`,
`packages/cli/tests/git-socket.test.mjs`, `packages/cli/tests/lifecycle.test.mjs` (the last approved in
mos-1370).
Proof: a GIT_TRACE2 run through a scratch PATH shim shows no maintenance child for the flagged helpers, and an
unflagged positive control shows one. Gate: Filbert approves. The four whole files, then the seat and cli
suites, are green on Sage's gate. Filbert's static APPROVE (filbert-v574-7da38224-sage, 01:23Z) adds proof
folds P1-P5: per-process GIT_TRACE2_EVENT files, the positive control in the gitEnv-shaped environment, a wait
for detached git before counting, the commit count beside the zero child count, and checkout.test's wrappers
through the shim. Residual outside 574: `packages/queue/src/store.mjs`, `packages/discord/src/git.mjs` and
`packages/harness/src/tools.mjs` commit without the maintenance flags. No whole-root test covers them today,
so that's a bounded follow-up note, not part of this row.
## Brief: 573, the P10 hop band (Vikunja 573)
Issue #1806.
Owner Gander, after 574. Reviewer Filbert. Gate owner Sage. Ruled in mos-1367 A5. 270+271 run 3 went red on
`packages/cli/tests/case1-accept.test.mjs:368/:390`, the P10 inner hop band (probe 1.08 under 1.25), at load.
Gander's packet states the band's source and a fix that measures the same property under load. Changing an
old test, or anything that loosens its assertion, goes to Mos before the build (mos-1272). Gate: Filbert
approves. The whole file under load at least 3 times, then the cli suite, are green on Sage's gate.
+13 -6
View File
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
<!-- mosaic-queue:begin -->
Generated from `docs/plans/queue.json` revision 3368 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
Generated from `docs/plans/queue.json` revision 3436 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|---|---|---|---|---|---|---|---|---|
@@ -244,7 +244,7 @@ Generated from `docs/plans/queue.json` revision 3368 by `queue render`. Do not e
| 205 | 193b1a: the message filter (#1703; FED R3 and 193b1b build on it) | morgana; reviewers duckworth | #1703 | done | 196 done | Duckworth approves the manifest; bus suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1a, the message filter | Landed ce6a96ad6 fast-forward on 0fa7bf421 (3 commits, 4 paths, manifest 858463b1). Duckworth APPROVE r1 c30375 (signed :124 test diff, mos-1262). Gate: Morgana slot205 10 files, bus 652/0, M1 M11-M14 killed, 0 skips; Sage test-*.sh 10/10, 3 by-design skips. N1/N3 to 205b or R3, N2 to 193b2 brief, N4 = #1782 before any user thread. Rebase next: 268 r4, 194, 236, 253, 274. |
| 206 | 193b2: thread shares, schema 3k-3l after the #436b build (#1691; live needs a --migrate window with Jason) | launchpad; reviewers duckworth | #1704 | done | 205 done, 234 done, 236 done, 275 done | Duckworth approves the manifest; bus suite, cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b2, thread shares (schema 3l) | Landed 0cbe2e9b3 fast-forward on 17f415792 (10 commits, 22 paths), pushed 19:47Z. Duckworth APPROVE rev 3322 c30795 on 0cbe2e9b3; request rev 3320/3321 c30793 posted as jarvis. Gate (Launchpad, 9538e0fc8, tip differs in BUILD.md only): bus 850/0, cli 869/0, ten scripts/test-*.sh rc=0, touched files whole green, M2-M4 M7-M9 and Q2 killed. One placed fixture red (mos-1272). Schema 3l to 3m; live --migrate is Mos's go under Q173. |
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | megavolt; reviewers duckworth | #1705 | done | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | LANDED c4f77373f 22:49Z 10-22 (fast-forward from f0e3dcdf4). Rebuild of gated 9bb406c6c: range-diff 4/4 =, patch-ids equal, 4 manifest blobs equal. Duckworth APPROVE c30849 rev 3356. Gate on 712f79c0a under sage-3041: access.test 31/31, M1-M8 killed, cli 894/894, ten scripts green. N1 DEFERRED (next access.test row). No live change. |
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | morgana; reviewers duckworth | #1706 | queued | 192 done, 206 done | Duckworth approves the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | — |
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | morgana; reviewers duckworth | #1706 | queued | 192 done, 206 done, 294 done | Duckworth approves the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | — |
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R8, the hub window on 10.1.1.54 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
| 210 | Agent host access with sudo through the credential store: grant per agent and host, the stack does ssh and sudo, the agent never sees the key (#1707, Vikunja #424, Q183/Q184, due 10-27; chained on row 199's candidate) | gizmoduck; reviewers duckworth | #1707 | done | — | Duckworth approves DESIGN.md, then the manifest; bus, business, cli suites and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_host-credentials.md` § Brief: agent host access with sudo through the credential store (#424) | Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree. |
| 211 | Force-push guard: scripts/git-hooks/pre-push refuses non-fast-forward and delete pushes from every worktree, and scripts/slot-busy.sh exits 1 on BUSY (#1709, Vikunja #426, mos-1078/1083) | darkwing; reviewers dewey | #1709 | in-progress | — | Dewey approves the manifest; the new or changed scripts/test-*.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; hook installed in the canonical checkout with a scripted refused force-push (sage) | `docs/plans/2026-10-20_force-push-guard.md` § Brief: a pre-push hook that refuses force-pushes, and a busy check that fails on BUSY (#426) | — |
@@ -306,9 +306,9 @@ Generated from `docs/plans/queue.json` revision 3368 by `queue render`. Do not e
| 267 | Case 2 C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | morgana; reviewers filbert | #1772 | queued | 216 done, 217 done, 224 done | Filbert approves the manifest. The brief's tests and the end-to-end crossover run whole plus every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | R8 accepted (sage-2218). Webby's R8 noise note (gosalyn-0127) is for the builder. |
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | done | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | Landed c27207f6e fast-forward on ce6a96ad6 (1 commit, 9 paths, digest caa0fa4e). r4 is a carry of r3 aca53c4f0: range-diff =, patch-id 2942acc8 equal, blobs equal. Webby APPROVE round 2 on ecd9b35eb stands; carries webby-0112 (r3) and webby-0113 (r4, notes 34e1ab2bf). Gate: Gizmoduck r4 rerun brief 14/14, library 83/83, business 209/209, 0 skips; gate grep empty. 194 rebases next (business.test.mjs). |
| 269 | 264b T1 item 7: per-session native store, workspace and single-writer fence | gizmoduck; reviewers duckworth | #1774 | done | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T1 run half, T2 workspace and path mutants; whichever of 191 and 264b lands second wires 191's admission checks (Q5). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | Landed efcc9ea8d fast-forward on 94c33a43c (13 commits, 12 paths, manifest 455c12ee). r3 is a carry of r2 aafcc857c, range-diff = (Duckworth 0194, c30756, rev 3307). Gate r3 18:37-18:50Z all green, 0 skips: bus 834, seat 172, cli 869, case2-unit 43, singles 38. M1-M12 kills carry. Re-pin needs the Console restart by Jason or Mos right after the host. A 264a rollback refuses a v2 sessions.json, see BUILD.md Rollback. |
| 270 | 264c T1 item 7: compaction count, exact for Pi | gizmoduck; reviewers duckworth | #1775 | in-review, round 1, request posted | 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Test T3 Pi half; Claude shows unknown with the Vikunja 466 reason (Q6). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 271 | 264d T1 item 7: clear context through row 194's lifecycle stop | gizmoduck; reviewers duckworth | #1776 | in-review, round 1, request posted | 194 done, 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T4 and T6; rebased onto 194's landing with no 194 code copied (Q4). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 272 | 264e T1 item 7: mosaic session CLI and Console chooser | huey; reviewers scrooge | #1777 | briefed | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T5 legacy single human and T7 Console half; controls for unlanded verbs refuse with a reason (Q1, Q7). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 270 | 264c T1 item 7: compaction count, exact for Pi | gizmoduck; reviewers duckworth | #1775 | done | 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Test T3 Pi half; Claude shows unknown with the Vikunja 466 reason (Q6). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | LANDED 7328da8ca (with 271, mos-1351) 02:13Z 10-23 on 767bb62c7: 33 commits cherry-picked from gizmoduck/row270-271-r4 49cee846e (base 25c09f19f), range-diff all '=', 40 paths identical to 49cee846e. Manifest f4174458 (15 paths) matches the tip. Duckworth APPROVE c30930 rev 3389. C2 run 5 GREEN on 53406f3e4: bus 873, seat 202, cli 905, ten scripts, 38/38 mutants killed. #1775 closes. |
| 271 | 264d T1 item 7: clear context through row 194's lifecycle stop | gizmoduck; reviewers duckworth | #1776 | done | 194 done, 269 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T4 and T6; rebased onto 194's landing with no 194 code copied (Q4). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | LANDED 7328da8ca (with 270, mos-1351) 02:13Z 10-23: tip 2b964a082 is in the 33-commit pick, range-diff all '='. Manifest 3ab1f5c8 (33 paths): 29 match the tip; BUILD.md(270), bus README, launcher.mjs and compactions.mjs moved under 270 and match 270's f4174458. Duckworth APPROVE rev 3380. Combined C2 run 5 GREEN, 271 M1-M14 killed. #1776 closes. |
| 272 | 264e T1 item 7: mosaic session CLI and Console chooser | huey; reviewers scrooge | #1777 | in-review, round 1, request posted | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T5 legacy single human and T7 Console half; controls for unlanded verbs refuse with a reason (Q1, Q7). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | — |
| 273 | Q175 phase 2: stack-release.sh activate --send and --no-sso (F39, mos-1246) | darkwing; reviewers filbert | #1778 | done | — | Filbert approves the manifest. stack-release.test.mjs and stack-release-external.test.mjs run whole plus scripts/test-release.sh on Sage's slot, and the brief's mutants are killed. (sage) | `docs/plans/2026-10-22_activate-send-nosso.md` § Brief: activate --send and --no-sso | landed 0bbf0f6d1 03:35:55Z, FF of 378a76b85; rebase of cac7d0f67, 6 range-diff pairs =, patch-ids equal, manifest e1031023 unchanged. Gate by Darkwing on 0bbf0f6d1: stack-release.test 218/218, external 25/25, test-release.sh rc 0, T58 c0 pass c1 fail, M1-M7 killed. Filbert APPROVE rev 3111 c30308, carry c30319. Docs 5365adaef (TOOLS.md N1 + BUILD-LOG). #1778. |
| 274 | FED R3: broker user principal (#414) | morgana; reviewers dewey | #1779 | done | — | Dewey approves the manifest. The bus and harness suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_federation-r3.md` § Brief: FED R3, the broker user principal | LANDED 4c485c63b ff from 47a35bf43 (6 Morgana commits). Dewey APPROVE c30487 on 4c485c63b (manifest 00047b52, 13 paths). Gate 10:09-10:22Z: 23 touched files 0 fail, bus 787, harness 151, business 228, cli 832, seat 167, 10 scripts rc=0 (skips test-queue 1, test-task 2), 22/22 mutants. M3x wall code not-holder (kit comment said human-required). r5 f58cfc361 red, void. Overlap 13/257 clean. |
| 275 | 193b1b: viewer verbs and board refusals (also after FED R4 when filed) | morgana; reviewers duckworth | #1780 | done | 205 done, 274 done | Duckworth approves the manifest. The bus suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b1b, viewer verbs and board refusals | Landed 3410df7c3 on refactor (fast-forward from 48e450ce8, 3 commits; landed sha = candidate, so range-diff and patch-id are identical by construction). Manifest 39847b15 (4 paths) blobs match the commit. Gate: whole files 7/22/6, bus 834, cli 861, harness 151, business 231, seat 167, 0 fail; 10 scripts/test-*.sh rc 0 with 3 named skips; M6 and M10 killed. Duckworth APPROVE round 1, Gitea #1780 comment 30717. |
@@ -328,7 +328,14 @@ Generated from `docs/plans/queue.json` revision 3368 by `queue render`. Do not e
| 289 | Case 2 C2-11b Console library pages and her saves (Haiku subagent, decision 129) | researcher; reviewers filbert | #1794 | briefed | 214 done, 288 done | Filbert approves the manifest. The webui suite and every scripts/test-*.sh are green on Sage's gate. Grep for hash pins on bus.js and serve.mjs before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-11b Console library pages and her saves | — |
| 290 | Case 2 C2-12b the setup sheet rewrite for free chat, supporting folders and saves | louie; reviewers webby | #1795 | briefed | 217 done, 218 done, 285 done, 291 done | Doc only. Webby approves it against the ruling. Before landing, grep packages/*/tests and scripts/test-*.sh to confirm no test reads the file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12b the setup sheet rewrite for free chat, supporting folders and saves | — |
| 291 | PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | gosalyn; reviewers webby | #1796 | in-review, round 1, request posted | 282 done | Webby approves the text. Jason says yes to the exact text (Mos asks him, mos-1328 step 3); no landing before that. requirements.test.mjs and every scripts/test-*.sh that reads the PRD or requirements.json are green on Sage's gate. Mos gets the landing commit. (sage) | `docs/plans/2026-10-22_prd-1-16-brief.md` § PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | — |
| 292 | stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | gander; reviewers duckworth | #1797 | briefed | 203 done, 270 done, 271 done | Duckworth approves the manifest. The touched test files run whole, the cli suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_config-checks-any-key-brief.md` § stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | — |
| 292 | stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | gizmoduck; reviewers duckworth | #1797 | in-review, round 1, request posted | 203 done, 270 done, 271 done | Duckworth approves the manifest. The touched test files run whole, the cli suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_config-checks-any-key-brief.md` § stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | — |
| 293 | 574: maintenance-free test fixture commits (Vikunja 574) | gander; reviewers filbert | #1805 | in-review, round 1, request posted | — | Filbert approves. The four whole files, then the seat and cli suites, are green on Sage's gate. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: 574, maintenance-free test fixture commits (Vikunja 574) | — |
| 294 | FED R6: Console granted-user sign-in, user-cli transport, peer links | launchpad; reviewers duckworth | #1800 | in-review, round 1, request posted | — | Duckworth approves the manifest. The webui, cli and bus suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: FED R6, Console granted-user sign-in | — |
| 295 | FED R4: revocation | morgana; reviewers dewey | #1801 | in-review, round 1, request posted | — | Dewey approves the manifest. The bus, cli and business suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: FED R4, revocation | — |
| 296 | FED R7: scratch two-instance acceptance | quackerjack; reviewers filbert | #1802 | briefed | 190 done, 294 done, 295 done | Filbert approves the manifest. The script test and every scripts/test-*.sh are green on Sage's gate, and no unit, port or socket of the script's outlives the run. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: FED R7, scratch two-instance acceptance | — |
| 297 | GitHub grant method (Bushroot's half of #436b) | bushroot; reviewers dewey | #1803 | briefed | 198 done | Dewey approves the manifest. The touched suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: the GitHub grant method (Bushroot's half of #436b) | — |
| 298 | Q228: chief-of-staff grant scope | megavolt; reviewers dewey | #1804 | briefed | 252 done | Dewey approves the manifest. The touched suites and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: the chief-of-staff grant scope (Q228) | — |
| 299 | 573: the P10 hop band (Vikunja 573) | gander; reviewers filbert | #1806 | briefed | 293 done | Filbert approves. The whole file under load at least 3 times, then the cli suite, are green on Sage's gate. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: 573, the P10 hop band (Vikunja 573) | — |
<!-- mosaic-queue:end -->
+2528 -27
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -246,7 +246,7 @@ a receipt with empty `changes`. A refusal is `action.refused` with its code alon
| `thread.messages` | `{thread,session?,after?,limit?}`; human sessions only. The session's messages oldest first, after the message `after` names (`message-not-found` when it isn't in the session), at most `limit` (default 50, 1 to 200). A thread shared with the caller reads too, any session of it (row 206). Writes nothing. |
| `thread.share`, `thread.unshare` | `{thread,to}`; the thread's owner only (row 206, #1704). `to` is `user:<id>`, the business human's name or `*`; yourself refuses `invalid-request`. A share refuses `share-exists` when one is in force, and `share-not-covered` when a `user:` target has no live grant covering the thread. An unshare refuses `share-not-found` when none is. Each writes one `thread_shares` row. Returns `{thread,to,op}`. |
| `thread.shares` | `{thread}`; the thread's owner only. The shares in force, `[{to,at,by}]`, oldest first. Writes nothing. |
| `session.list` | `{thread}`; human and tester sessions (row 264a, #1769). The thread's sessions oldest first, `[{id,at,name,predecessor,default}]`. Writes nothing. |
| `session.list` | `{thread}`; human and tester sessions (row 264a, #1769). The thread's sessions oldest first, `[{id,at,name,predecessor,default,compactions}]`. `compactions` is `{count,known,reason,at}` from the host's ledger (row 270, #1775): `known` true gives the exact count of completed Pi compactions; otherwise `count` is a lower bound or null and `reason` is `harness-unsupported`, `not-collected`, `store-unreadable` or `store-incomplete`. `not-collected` passes: the next collection reads on. A launcher killed mid-write leaves a torn ledger line, and the count stays unknown (`store-incomplete`) for good, by design (sage-2806 condition 3). Writes nothing. |
| `session.create` | `{thread,name,op}`; human and tester sessions. A new session of the thread, metadata only. A retry with the same `op` and name returns the first row; another name under that `op` refuses `session-op-conflict`, and a taken name `session-exists`. Returns `{id,thread,name}`. |
### Projects and threads (row 121, #1615)
+37
View File
@@ -493,3 +493,40 @@ CREATE INDEX thread_shares_thread ON thread_shares (thread, target, seq);
CREATE TRIGGER thread_shares_no_update BEFORE UPDATE ON thread_shares BEGIN SELECT RAISE(ABORT, 'thread_shares is append-only'); END;
CREATE TRIGGER thread_shares_no_delete BEFORE DELETE ON thread_shares BEGIN SELECT RAISE(ABORT, 'thread_shares is append-only'); END;
CREATE TRIGGER thread_shares_no_replace BEFORE INSERT ON thread_shares WHEN EXISTS (SELECT 1 FROM thread_shares WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'thread_shares is append-only'); END;
CREATE TABLE session_ops (
seq INTEGER PRIMARY KEY AUTOINCREMENT,
at TEXT NOT NULL,
business TEXT NOT NULL,
thread TEXT NOT NULL REFERENCES threads(id),
session TEXT NOT NULL REFERENCES sessions(id),
op TEXT NOT NULL,
stage TEXT NOT NULL CHECK (stage IN ('requested','drained','uncertain','exited','retired','failed','cancelled','start-held','start-started','start-refused')),
attempt INTEGER NOT NULL CHECK (CASE WHEN stage GLOB 'start-*' THEN attempt >= 1 ELSE attempt = 0 END),
work_attempt INTEGER CHECK (work_attempt IS NULL OR (stage GLOB 'start-*' AND work_attempt >= 1)),
successor TEXT NOT NULL,
successor_name TEXT NOT NULL,
run TEXT,
generation INTEGER CHECK (generation IS NULL OR generation >= 1),
digest TEXT,
refusal TEXT,
UNIQUE (thread, op, stage, attempt)
) STRICT;
CREATE TABLE message_cancels (
seq INTEGER PRIMARY KEY AUTOINCREMENT,
at TEXT NOT NULL,
message TEXT NOT NULL UNIQUE REFERENCES messages(id),
session TEXT NOT NULL REFERENCES sessions(id),
op TEXT NOT NULL
) STRICT;
CREATE INDEX session_ops_session ON session_ops (session, seq);
CREATE TRIGGER session_ops_no_update BEFORE UPDATE ON session_ops BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
CREATE TRIGGER session_ops_no_delete BEFORE DELETE ON session_ops BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
CREATE TRIGGER session_ops_no_replace BEFORE INSERT ON session_ops WHEN EXISTS (SELECT 1 FROM session_ops WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'session_ops is append-only'); END;
CREATE TRIGGER session_ops_one_open BEFORE INSERT ON session_ops
WHEN NEW.stage = 'requested' AND EXISTS (SELECT 1 FROM session_ops o WHERE o.session = NEW.session
AND o.seq = (SELECT max(seq) FROM session_ops WHERE thread = o.thread AND op = o.op)
AND o.stage IN ('requested','drained','uncertain','exited'))
BEGIN SELECT RAISE(ABORT, 'session has an open clear'); END;
CREATE TRIGGER message_cancels_no_update BEFORE UPDATE ON message_cancels BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
CREATE TRIGGER message_cancels_no_delete BEFORE DELETE ON message_cancels BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
CREATE TRIGGER message_cancels_no_replace BEFORE INSERT ON message_cancels WHEN EXISTS (SELECT 1 FROM message_cancels WHERE seq = NEW.seq) BEGIN SELECT RAISE(ABORT, 'message_cancels is append-only'); END;
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff. Show more