Compare commits

...
Author SHA1 Message Date
jason.woltje 94128f73f7 Untested full gate: row 225 manifest and 14 verified mutation kills 2026-10-23 14:30:30 -05:00
jason.woltje 5079012771 Untested full gate: row 225 mutation check asserts after repaint completion 2026-10-23 14:28:08 -05:00
jason.woltje 77172ce3b4 Untested full gate: row 225 fixes verified by targeted RED-GREEN and library suite 2026-10-23 14:26:33 -05:00
jason.woltje 1afcfc766d Untested: record mos-1510 preservation condition and row 225 test slot 2026-10-23 14:18:50 -05:00
jason.woltje 4732821f7a Untested: row 225 isolated mutation kit and whitespace-clean approval evidence 2026-10-23 14:17:28 -05:00
jason.woltje 8d37d21cb8 Untested: fethry row 225 handoff recovery and contract regression coverage 2026-10-23 14:14:07 -05:00
jason.woltje 9a131e9567 morgana: row 225 handoff to fethry (mos-1501), UNTESTED: renderReply, chat replies/cites/notice/Revert/status line, library status and revert routes, channel-proof re-pin of webui bus.mjs 2026-10-23 14:10:18 -05:00
jason.woltje 66f7becf21 morgana: row 225 BUILD.md: check-result shape agreed with 217 (launchpad-0118), library.revert contract A2 for row 288, changed-since via file and last (sage-3596) 2026-10-23 14:02:02 -05:00
jason.woltje 45dca69f6e sessions: Sage 10-23 Vikunja 571 W rows 309-318 filed, 571 done 2026-10-23 13:19:06 -05:00
jason.woltje 293b42fee9 queue: revs 3617-3636: Vikunja 571 W build rows 309-318 added (sage 3617-3626: W1, W2, W3a, W3b, W6, W3, W4b, W5, W7, W8, post-10-27, brief docs/plans/2026-10-21_q195-writing-editor.md) and owners set to unassigned (sage 3627-3636) 2026-10-23 13:16:56 -05:00
jason.woltje 369607e92d sessions: Sage 10-23 rows 308 and 252 landings, 571 Connections design amendment, Vikunja 597; Scrooge row 252 line 2026-10-23 12:51:36 -05:00
jason.woltje 59f013247c queue: revs 3589-3616: row 306 in-progress, in-review and failed request (morgana 3589, 3592-3593), request (sage 3594-3595, comment 31207), approve record (dewey 3596), note and done (sage 3599-3600); row 308 added and briefed (sage 3590-3591), claim, in-review and failed request (gosalyn 3601-3603), request (sage 3604-3605, comment 31223), approve record (webby 3606, comment 31224), note and done (sage 3613-3614, landed 30e6bb680); row 291 note and done (sage 3597-3598); row 252 issue #1753 (sage 3607), in-review and failed request (scrooge 3608-3609), request (sage 3610-3611, comment 31230), approve record (webby 3612, comment 31229), note and done (sage 3615-3616, landed 45ea3fa0d) 2026-10-23 12:51:27 -05:00
jason.woltje e8bb13df84 docs: clarify Connections opening order and current-link filtering 2026-10-23 12:47:20 -05:00
jason.woltje dba2ebdb85 docs: plan required Console Connections view before real import 2026-10-23 12:47:19 -05:00
jason.woltje 45ea3fa0d2 row 252: library-cli and thread-claims bus.mjs stubs gain the throwing consoleCall export cli.mjs now imports (Mos GO mos-1465 under mos-1272, via sage-3497) 2026-10-23 12:25:40 -05:00
jason.woltje f69c962009 row 252: decide-sso J19 killer (a granted user's config says decide false, its decision POSTs stop at owner-only), J16 checks the config that starts without send; BUILD.md rulings, J11c and J18 equivalence, part 2 mutants (#1753) 2026-10-23 12:25:40 -05:00
jason.woltje 5b6c878143 row 252: decide-browser placements (mos-1272): the hostile grant goes in with its INSERT since decisions is append-only, and page() loads about:blank first so a same-URL visit reloads 2026-10-23 12:25:40 -05:00
jason.woltje 8c2c5303cf row 252: access-boot fixture provider gains issuer https://idp.test, so consoleOwners can key the owner (Mos GO mos-1464 under mos-1272, via sage-3494) 2026-10-23 12:25:40 -05:00
jason.woltje 23e3d09fa9 row 252: decide-sso first-run reds placed (mos-1272): a decision transport without --send never starts, and a POST to /api/bus/decision.* meets row 74's read-only refusal; BUILD.md run notes (#1753) 2026-10-23 12:25:40 -05:00
jason.woltje 795a645e65 row 252: console-decide events() keeps the row kind as event, since an answer body's kind shadowed it (first-run red at :180, two vacuous zero counts), and a J7 killer test for the console cap release over the socket (#1753) 2026-10-23 12:25:40 -05:00
jason.woltje 076ca8d6e4 row 252 BUILD.md: rebase onto fe4ee8c7b, commit map and the six conflict resolutions (sage-3482) 2026-10-23 12:25:40 -05:00
jason.woltje 68be863248 row 252: mos-1321, reword the shell.test write-boundary comment so Approve and Deny is the one decision control; BUILD.md records the ruling and its channel-proof review condition 2026-10-23 12:25:40 -05:00
jason.woltje 264fad2e26 row 252 BUILD.md: channel-proof overlap with row 239 (sage-2835) 2026-10-23 12:25:40 -05:00
jason.woltje 24672fd9d6 row 252: Console inbox Approve and Deny for grant requests, human-only through a console owner proof, confirmation then resolve, via console with human.input before the executor (#1753, Webby N1-N5); tests not yet run 2026-10-23 12:25:39 -05:00
jason.woltje 30e6bb6803 gosalyn: PRD 1.17, Case 2 story files are DOCX (row 308, sage-3516, Q235 to Q237, Q239). Applies the approved d9 text at 4607e15b8 word for word to the 1.16 text. Status, the header sentence and a 1.17 source line, the Creative library lead, REQ-LIB-1, -2, -4, -5 and -6 changed as d9 says, new REQ-LIB-8 for the writing store and REQ-LIB-9 for the Console's DOCX editor, the 1.17 notes and four risks. requirements.json goes to 1.17 with 87 ids and the d9 titles, and the tasks and bus test pins follow 2026-10-23 12:20:30 -05:00
jason.woltje 7defb58e97 sessions: Sage 10-23 rows 291 and 306 landings, row 308 launch 2026-10-23 11:37:34 -05:00
jason.woltje 9c8877bdf3 plans: DEFERRED row 306 N1 (revoke Set lost on broker restart) and N2 (pending limit stop answers not-holder first), landed 2d0318a44 (mos-1463) 2026-10-23 11:35:21 -05:00
jason.woltje 2d0318a44a morgana: row 306 build (#1813): #accessRevoked from #revokeLost, #agent answers thread-revoked until endLaunch, runner stops on thread-revoked, READMEs, access-revoke.test 7 7 (mos-1463) 2026-10-23 11:33:17 -05:00
jason.woltje bd3fa2a484 morgana: row 306 red-first tests (#1813): bus revoke-window T1/T2, cli revoke-window T3 forcing test; no product code 2026-10-23 11:33:17 -05:00
jason.woltje ab575ee553 morgana: row 306 design r2 (dewey-0918 C1-C3, sage-3483/mos-1463): T3 asserts no thread-revoked event for A, T2 limit half makes a second call, six-suite gate, access-revoke edit bounded to 7 7 2026-10-23 11:33:17 -05:00
jason.woltje 1676ad0de8 morgana: row 306 design r1 (#1813, sage-3478): #accessRevoked set from #revokeLost, #agent answers thread-revoked until endLaunch, runner maps it to stopped; T1-T3, M1-M3, six access-revoke asserts to Mos via Sage 2026-10-23 11:33:17 -05:00
jason.woltje 4874d1e83a gosalyn: PRD 1.16 routes library.write under REQ-LIB-6 (mos-1328, mos-1329, sage-2919, webby-0164 B2). REQ-WAIT-3 gains the routing paragraph with its bounds, creative roles only and within-role, Markdown under supporting folders a human marks, never a story file, the writing agent's live Gitea grant on the library project, one commit per write by its own account with the trailer, notice and revert. REQ-LIB-6 names the action. Status, the header, a 1.16 source line and a 1.16 note. requirements.json changes its version only, with the test pin 2026-10-23 11:15:49 -05:00
jason.woltje e909ef51a2 plans: PRD 1.17 brief (Gitea #1815, Vikunja 570): Gosalyn builds d9 4607e15b8 onto 1.16 after row 291, Webby reviews, four files, Sage gates 2026-10-23 10:44:58 -05:00
jason.woltje 4607e15b83 sage: PRD 1.17 d9 r3 (02f28dec), Mos's J-b check mos-1470: J-b records N20 as confirmed, REQ-LIB-5's version tag cites Q239 for the Connections view (N21), d9 changes names both 2026-10-23 10:41:08 -05:00
jason.woltje 8b42010551 sage: PRD 1.17 d9 round 2 (b17ce386), Webby round 6 B1: REQ-LIB-5, its note, the Risks line and the A6 line take mos-1443 R3's text as written; J-b in the past tense (N19); the import-waits line cited as Mos's R3 default (N20) 2026-10-23 10:36:16 -05:00
jason.woltje 4863e2caf4 sage: PRD 1.17 draft 9 (7dfc52fd), d8 plus Jason's Q41 O2 (Q239, mos-1467): REQ-LIB-5 requires the Console Connections view for converted chapters, note and Risks line follow, J-b records the answer; Webby round 5 N15-N17 2026-10-23 10:25:37 -05:00
jason.woltje 7b25a60633 agents: webui runs set MOSAIC_REQUIRE_EDITOR_BUILD=1 and linked worktrees npm ci packages/webui (Vikunja 597) 2026-10-23 10:01:25 -05:00
jason.woltje e86df37d76 sessions: Sage 272 and 307 landings, 306 design, 252 rebase check 2026-10-23 09:59:29 -05:00
jason.woltje fe4ee8c7b5 queue: revs 3582-3588: row 307 in-review and failed request (gizmoduck 3582-3583), request (sage 3584-3585, comment 31189), round 1 approve record (duckworth 3586), landing note and done at d243f6b25 (sage 3587-3588); queue only 2026-10-23 09:42:05 -05:00
jason.woltje 0621c439e0 plans: DEFERRED gains row 307 N1 (row 270's store-unreadable doesn't stick; #1814 landed d243f6b25) 2026-10-23 09:40:06 -05:00
jason.woltje d243f6b25f gizmoduck: row 307 run logs: capture.mjs makes each fixture's data root before sessionStore (base session.mjs:78 needs it), BUILD.md expected red adds claude-history test B (duckworth-0287), steps 1-6 results on 1f6db1589 and 1f00d4f91 with skips named and M1-M4 killed (sage-3455, sage-3465) 2026-10-23 09:37:50 -05:00
jason.woltje 869670d1b1 gizmoduck: row 307 BUILD.md: commits, old-test edits against mos-1459, new tests and the empty-session correction to test B, expected red on 1f6db1589, run plan with mos-1275 limits, mutants M1-M4 2026-10-23 09:37:50 -05:00
jason.woltje dbbda25373 gizmoduck: row 307 (#1814, Vikunja 598) harness kind gains compactions: pi's stream scan and Claude-history check move to packages/plugins/harness/pi/compactions.mjs, claude-code answers harness-unsupported, core keeps the ledger and checks each answer, readLedger reads the reason only, the launcher passes the loaded module; four old-test edits under mos-1459 2026-10-23 09:37:50 -05:00
jason.woltje a9da046f83 gizmoduck: row 307 (#1814, Vikunja 598) new tests, red first: row 270's ledgers per reason read the same count and the next collection goes through the harness module, core refuses an answer out of contract, pi answers what base scanPi read, claude-code answers harness-unsupported, a module without compactions refuses missing-export; capture.mjs checks the literals against base 1e629c892 2026-10-23 09:37:49 -05:00
jason.woltje 4a71b56abc gizmoduck: row 307 design r1 notes: seat suite whole and compactions-launch T3 run-not-skip in the gate (Duckworth C1, C2), launcher closure comment on bypassing call() (N1), line refs :301 and :302 (N2) (duckworth-0280) 2026-10-23 09:37:49 -05:00
jason.woltje 5208894196 gizmoduck: row 307 (#1814, Vikunja 598) design: harness export compactions({ store, cursors, budget, streams, first }), interfaceVersion stays 1, core keeps the ledger, readLedger reads the reason, ledger compatibility table, four old-test edits for Mos (sage-3440) 2026-10-23 09:37:49 -05:00
jason.woltje 8dc132bec8 queue: revs 3572-3581: row 272 round 3 approve record (scrooge 3572, comment 31177), row 307 claim (gizmoduck 3573), row 272 round 4 moves (huey 3574-3576), request (sage 3577-3578, comment 31183), round 4 approve record (scrooge 3579, comment 31184), landing note and done at d33989230 (sage 3580-3581); queue only 2026-10-23 08:58:25 -05:00
jason.woltje 08b8831bce plans: DEFERRED gains row 272 N1 (user session verbs) and the row 270 N6 rebound after row 307 (#1777 landed d33989230) 2026-10-23 08:56:47 -05:00
jason.woltje d339892301 row 272: scope unavailable-session waits to the chat area 2026-10-23 08:53:07 -05:00
jason.woltje 0ea3db297b row 272: fix session fixture authority and initial chat render 2026-10-23 08:53:07 -05:00
jason.woltje f859d06d20 row 272: preserve granted-user default chat on denied session catalog 2026-10-23 08:53:07 -05:00
jason.woltje 70651f45d8 row 272: retain session recovery state on invalid selection 2026-10-23 08:53:07 -05:00
jason.woltje 490f45f024 row 272: preserve untested session CLI and Console draft (sage-3145) 2026-10-23 08:53:07 -05:00
jason.woltje 1e629c892d queue: revs 3565-3571: row 272 round 3 moves on 72690cae7 (huey 3565-3567) and request (sage 3569-3570, comment 31174); row 307 added and briefed (#1814, Vikunja 598, mos-1455 item 3; sage 3568, 3571); queue only 2026-10-23 08:08:15 -05:00
jason.woltje 84de3fbca3 plans: row 270 pi compaction reading moves behind the harness kind (#1814, Vikunja 598, mos-1455 item 3, Gizmoduck, reviewer Duckworth); gate template runs all five suites and sets MOSAIC_REQUIRE_EDITOR_BUILD=1 (mos-1455 item 4, mos-1453) 2026-10-23 08:02:15 -05:00
jason.woltje 1e4c1e6e94 queue: revs 3539-3564: rows 300 and 301 round 1 claims and in-review moves (launchpad 3539-3544), round 1 requests (sage 3545-3548), round 2 moves (launchpad 3549-3554), round 2 requests (sage 3555-3558), Duckworth approve records 300 (3559) and 301 (3560), landing notes and done at 07315e651 and 5b846b048 (sage 3561-3564); queue only 2026-10-23 07:33:26 -05:00
jason.woltje 5b846b048a launchpad: row 301 (#1808): library-cli and thread-claims bus.mjs fixtures export throwing busBusiness and userCaller stubs (mos-1451 under mos-1272); BUILD.md repaired with run 1 and the ruling; manifest seven paths; unrun 2026-10-23 07:26:44 -05:00
jason.woltje 59f0e44f47 launchpad: row 301 (#1808): channel-proof re-pins webui bus.mjs at 39fcb27c for the user transport fold, humanCall unchanged (mos-1449 under mos-1272); unrun 2026-10-23 07:26:44 -05:00
jason.woltje bb031ec840 launchpad: row 301 (#1808): R6-N4, user-bus.mjs folded into bus.mjs beside humanCall, unchanged; channel-proof re-pin held for Mos's go; unrun 2026-10-23 07:26:44 -05:00
jason.woltje 07315e6514 launchpad: row 300 (#1807): run 1 gate results in BUILD.md (1/0, webui 375/0, M12 killed) and Duckworth's N1; packet only 2026-10-23 07:26:44 -05:00
jason.woltje fb04cbc431 launchpad: row 300 (#1807): run 1 first-run red placed (mos-1272), the test's call helper resolves headers again as granted-user.test does; manifest re-hashed 2026-10-23 07:26:44 -05:00
jason.woltje 9d929911c8 launchpad: row 300 (#1807): R6-N1, an HTTP-level test of the user-session option swap with the userRoute gate stubbed open by a load hook, and mutant M12; unrun 2026-10-23 07:26:44 -05:00
jason.woltje bcf3c1dacf queue: revs 3531-3538: row 303 landing note and done at e551cd846 (sage 3531/3532); row 304 approve record (dewey 3533), row 305 approve record (filbert 3534); row 304 landing note and done at a863015a5 (sage 3535/3536); row 305 landing note and done at d52d9f1f2 (sage 3537/3538); queue only 2026-10-23 06:54:46 -05:00
jason.woltje d52d9f1f2f row 305 (#1812): gate run 1 evidence (sage-3374): both suites green, mutants a and b killed in both, c a known survivor 2026-10-23 06:49:49 -05:00
jason.woltje 830f3d8bff row 305 (#1812): BUILD.md notes the SIGKILL leak and its cleanup (Filbert N1) 2026-10-23 06:49:49 -05:00
jason.woltje e48d602165 row 305 (#1812): BUILD.md with the trace, change, gate and the three mos-1445 mutants 2026-10-23 06:49:49 -05:00
jason.woltje cabcb81d49 row 305 (#1812): each suite's network check also requires the network present under the run's project before the remove (mos-1445) 2026-10-23 06:49:49 -05:00
jason.woltje 353d65f577 row 305 (#1812, Vikunja 591) draft: per-run COMPOSE_PROJECT_NAME, exit trap removes that one network, one absent-network check per suite; for Mos through Sage before the build (mos-1272) 2026-10-23 06:49:49 -05:00
jason.woltje 2c289f6f0d SESSIONS: filbert row 305 round 1 approve (c31127, rev 3534) 2026-10-23 06:47:32 -05:00
Rocko a863015a51 test: match Claude memory slug normalization under mos-1442 2026-10-23 06:46:02 -05:00
jason.woltje e551cd8466 launchpad: row 303 (#1810): R9 asserts the rehearsal directory is set before the placeholders check joins it, so a null shows the rehearsal output (mos-1422, Vikunja 589) 2026-10-23 06:41:07 -05:00
jason.woltje c13c337e16 queue: revs 3521-3530: rows 304 and 305 claimed (rocko 3521, gizmoduck 3522); row 305 in-review on 7e8218a2e (gizmoduck 3523), request failed without a token (gizmoduck 3524), sage's request posted c31126 (3525/3526); row 304 in-review on 5e8354ebb (rocko 3527), request failed without a token (rocko 3528), sage's request posted c31128 (3529/3530); queue only 2026-10-23 06:27:36 -05:00
jason.woltje 435a7458d3 sage: PRD 1.17 draft 8 (343cff6f), d7 plus Mos's d8 fix list mos-1443 (R1-R14, M1-M45, J-i to J-l) and the mos-1446 choices (M10 default, M15 quotes Q235 in full, M40 both sentences); M15 and M16 cite b99ca86b9. Webby APPROVE round 5 webby-0210, no B items; N15-N17 carry to d9 2026-10-23 06:21:31 -05:00
jason.woltje b99ca86b9c docs: align editor conversion boundary and host restore with PRD d8 2026-10-23 06:08:20 -05:00
jason.woltje 0e057e05eb queue: revs 3511-3520: rows 304, 305 and 306 added and briefed (sage 3511-3516); row 272 round 2 in-review on b44332a93 (huey 3517), request failed without a token (huey 3518), sage's request posted c31120 (3519/3520); queue only (sage) 2026-10-23 06:02:42 -05:00
178 changed files with 19937 additions and 661 deletions

No files matched your search

+30
View File
@@ -5387,3 +5387,33 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault run minus a separate baseline run, and a loaded whole-suite run went red. Mos approved direct timing from host /proc/uptime stamps with ambient load only (mos-1409). After: rebased onto 80793d824 as ffa93b054 (range-diff 3/3 =, manifest 277ace0b unchanged). Gate 07:26Z-07:50Z: case1 whole 3x 53/0/0, cli whole 917/0/0 (59 files, load 1.54-7.17), P10 inner 2.27 s, outer 4.01 s, M1-M6 killed. M4's planned guard return then exact outer-reason failure counts as its kill (sage-3271, mos-1426). Filbert APPROVE c31084 rev 3485, N1 M1 margin 0.02 s. 299 LANDED ffb2fedfb 08:03Z, done rev 3487, #1806 closed c31087; queue commit 8a35ffadb (revs 3474-3487). Row 303: Duckworth APPROVE c31079 rev 3480 with C1, the whole-file gate. Rows 219 (bfa421b6c) and 295 r2 (b16cef1d0) rebases: Dewey static clean. Corrections: Launchpad and Gander have no token, and the CLI refuses another seat's token file, so I posted both requests as sage (c31082, c31083). My sage-3265 wording led Duckworth to poll queue show into Gander's slot (07:20Z-07:26Z); I ruled it ambient load (sage-3274).
2026-10-23T10:48:40Z | Sage, rows 302, 295 and 219 landings, the Docker pool diagnostic, rows for Vikunja 590 and 591 | Before: refactor 1ac655005. 302 (#1809, Gizmoduck, Dewey) LANDED 3ed9fd754 08:49Z: 7 commits onto 1ac655005, range-diff all =, tree = gated merge 783602e06; queue commit 07802dc57 (revs 3488-3494). Row 219's gate went 11/3 in test-release.sh: release activate's health check failed because Docker could not create sage-diag1437_default, since all predefined address pools were used up. Under mos-1437 I reproduced it on clean 07802dc57 (11/3, the same stderr). I then removed 18 unused gate _default networks, and the rerun went 14/0 with the run-task marker ok. Mos accepted the removal and ruled that a probe that can create something waits for the go and a removal waits for the report (mos-1438). 295 (#1801, Morgana, Dewey) round 2 gate on d68b4300d (manifest e94c9aeb) was green. It LANDED 2efd127ce 10:10Z: 9 commits onto 07802dc57, HEAD~1 tree = gated a86592ffc. Done rev 3502, #1801 c31105, P19 414 c911. 219 (#1720, Rocko, Dewey): the restart on the post-295 merge 054da642f (manifest 4a2271dd, my mos-1439 call) passed node 2523/0/2 and shells 449/0, with 24 mutants killed. It LANDED 5fdfd7275 10:40Z (8 row commits plus packet-only evidence, tree e86a84a85 = gated). Done rev 3509, #1720 c31110, P19 416 c913. Queue commit 90633c864 (revs 3495-3510). Brief docs/plans/2026-10-23_gate-env-rows.md names rows for 590 (claude-session slug) and 591 (compose network cleanup), and DEFERRED.md gets 219 N1 (U6). Row 272 (Huey) conflicts with 5fdfd7275 in code, so it moves back to in-progress for a rebase. Its new fallback is bounded to a session.list access-refused on an omitted-session route (sage-3357). Corrections: my network create probe ran before Mos's go, and the removal ran before the report. My first slot-order line put the diag start at 09:44Z; it was about 09:41Z. My 219 push piped its output through grep, but the after-fetch confirmed origin. Queue commit 90633c864 also carries Huey's rev 3510 (272 back to in-progress), and its message names only 3495-3509.
2026-10-23T11:03:46Z | Gyro Vikunja571 mos-1444 PRD d8 follow-on | Branch gyro/editor-571-d8 from origin/refactor c16e943b9. Both designs now keep library.convert outside ACTIONS/ROUTED and broker processing, retain REQ-WAIT-1/hard-wait test unchanged, constrain unmarked story eligibility to mapped book/playground scope, and add whole-host pre-import restore recovery from remote import manifests plus the Gitea archive commit. Lookups refuse until one current version is proven; VM1012 restore needs Jason present under Q185. W7 no longer owns hard-waits.test.mjs. Canonical queue next gyro returned nothing; direct Mos assignment governs. Static diff, exact replacement and append-only checks pass. No code, tests, build, browser, private-file or live action. Fenton re-check requested after authorized branch push.
2026-10-22 | Huey | Row 272 / 264e (#1777) preparation on huey/row272, base 2d0d407ba. Before: bus session metadata and launcher isolation exist, CLI/Console expose only the saved thread default. Draft: session CLI, Console chooser with isolated drafts/cursors/receipts, metadata counts and safe-clear operation receipts against Gizmoduck's 0260 contracts; narrow deferred B6 reply fence. Tests authored, not run: Gander row 253 holds Sage's slot; no Node, claim, HTTPS git, push or live changes. This entry records preparation after the first draft, not a green acceptance result.
2026-10-22 | Huey | Row 272 draft, 264d additive contract gizmoduck-0272 (78e14352f). Added confirmed cancellation through the human channel, cancelled receipt retirement distinction, named refusals and routed-message cancellation output. Before: cancelled was not recognized as terminal; after: draft CLI stops polling and Console shows the before/after retirement outcome. Acceptance remains unverified: no Node/tests/claim/commit/push under Sage's gate. No runtime/lifecycle authority expanded.
2026-10-23 | Huey | Row 272 / 264e sage-3141 local rebase. Before: uncommitted draft on 2d0d407ba. After: same draft on new huey/row272-b23783f39 at b23783f39, with 270/271 landed (7328da8ca) and schema 3n. Product changes auto-merged; both append-only logs retain the full new base and original Huey additions. Checked landed session.list and clear/cancel/operation envelopes against the wired draft: no incompatible contract change. Historical unavailable-dependency cases stay as stand-ins; actual base supplies counters and lifecycle state. No tests/Node/claim/commit/push while Gander holds row 574 run 2. Existing-test fixture/assertion delta is named in the packet for Mos's ruling via Sage before execution.
2026-10-23 | Huey | Row 272 sage-3145 preservation correction. refs/stash is shared, so it was unsafe as sole draft storage. Pinned exact stash commit 07dc9d2f7944b4781b5e80dfa18425f4749f7a45 to local huey/row272-stash-keep and left the entry untouched. Sage explicitly directed committing the rebased draft before testing to preserve it: an untested local checkpoint, no acceptance claim. No Node/test/queue/network/live action. Separate claims and test CLEARs remain pending; old-test delta is with Mos via sage-3144.
2026-10-23 | Huey | Row 272 early static review follow-up, scrooge-272-early-read-huey-1. Before: a bad-session 404 erased sibling draft/recovery caches, malformed clear/cancel write receipts omitted the audit line, and another session's op key produced an ambiguous error. Draft after: purge only on named thread-level refusals, write one redacted malformed-receipt audit line, and identify key reuse without a write. New browser/HTTP/CLI tests authored, unrun under test hold; approved old-test hunks unchanged. Published 850b22680/round-1 manifest unchanged; follow-up is uncommitted, no acceptance claim.
2026-10-23 | Huey | Row 272 round 2 on row 219 landing 5fdfd7275, after 295. Replayed round 1 and B1/N1/N2; seven conflicts resolved retaining both log histories, both test sets, 219 instance chooser and 295 owner-settings-only transport. Static comparison confirms old tests differ only by mos-1388 approved hunks. Sage3357/Mos1442 authorize bounded granted-user legacy fallback: only session.list access-refused and omitted-session route; no new session grants or owner routing. RED first under3358 on new file only: 1 expected legacy assertion failure, 3 guards pass, 3.504s, TMPDIR mktemp/no underscore, no compose network; slot returned as huey0035. Then implemented fallback and added timeout/bus/malformed guards plus isolated-copy mutant runner, all unrun afterward. Candidate commit/push precedes Sage's full gate by3354; no green claim.
2026-10-23 | Huey | Row 272 round 3 per mos-1455 / sage-3435. Gate run 1 on b44332a93 RED: bus 891/0, business 231/0, CLI 940/1, plugins 110/1, webui 372/20; no mutants. Fixed only new CLI fixture role authority and three null-old render guards. Catching browser tests unchanged. manifest.r3 freezes the same 17 paths. Authorized pre-gate follow-up commit/push; no local tests, Node or queue writes; separate compactions dependency and Sage gate remain pending.
2026-10-23 | Sage (1ef1e4f8) | Row 272 landing (#1777, 264e) | Gate r4 on bea0c8e1c over 1e629c892: bus 891/0, cli 941/0, webui 392/0, business 231/0; plugins 110/1, the single red being row 65 harness.test.mjs:192 naming compactions.mjs, which row 307 owned and fixed. M1-M6 and F1-F4 killed, restore clean. Landed d33989230, queue commit 8dc132bec.
2026-10-23 | Sage (1ef1e4f8) | Row 307 landing (#1814, Vikunja 598) | Six first-parent commits replayed onto 8dc132bec with matching patch-ids, 16 paths equal to 6fdfe3790, manifest digest 17c9f8d9 rebuilt from git. Gate green 14:37:21Z: bus 891/0, cli 941/0, plugins 119/0, webui 392/0, business 231/0, every other package 0 failed 0 skipped, every scripts/test-*.sh passed (skips: test-queue canonical root, test-task live provider x2). M1-M4 killed, restore clean. Landed d243f6b25, DEFERRED 307 N1 0621c439e, queue commit fe4ee8c7b. Correction: my first note said Vikunja 598 done when I had only commented (951); Mos closed it (952).
2026-10-23T17:29:46Z | Gyro Vikunja571 sage-3542 Connections amendment | Branch gyro/editor-connections-design cut exactly from landed PRD1.17 30e6bb680; read REQ-LIB-5 in that PRD. Replaced stale J-b waits in parent and W4. Proposed W8 owns current authorized Markdown/DOCX links/backlinks list, labels, versioned refresh, leakage refusals and fixture tests. Build order under mos-1470/comment961 is W7 closed routing candidate, W8 landing, then W7 human first-real-import opening. No REQ-LIB-8 condition or acceptance change. Q234/mos-1363/S0 M1-M14 and post-10-27/MVP priority fixed. Static diff, twelve brief sections and append-only checks pass. No repo Node, tests, build, browser, queue mutation or live action. Fenton reviews after authorized docs branch push; Sage lands on approval.
2026-10-23T17:32:37Z | Gyro sage-3544 fenton-0016 N1/N2 delta | Before: W7 Gate/final real-conversion list omit the W8 build-order cross-reference and W8 human-view filtering cites agent settings. After: both locations explicitly put W8 landing before human opening as mos-1470 build order, not acceptance; comment/tracked-deletion links are not current story links and never appear in the human Connections view regardless of agent settings. New commit atop approved 5af060fb5, no amend. Static diff and append-only checks pass. Docs only, no repo Node/tests/live action. Fenton checks only this delta.
2026-10-23 | Fethry | Row 225 C2-10b (#1726), handoff recovery. Before: Morgana's untested candidate 9a131e956 replaces renderItems with renderReply and adds Console citation/notice/status rendering and status/revert routes. Continuing that exact history on fethry/row225; preserving A1-A5 and the accepted file deviations. Remaining: confirm limits contract, review/test candidate under Sage CLEAR, obtain Mos OK through Sage for inherited old-test changes, manifest and Dewey review. No Node/tests run in this session and no green claim.
2026-10-23 | Fethry | Row 225 T1 lane A (sage-3641/3647). Before: nested replyLimits fallback/problem omitted, wrong-project revert receipt accepted, invalid library mapping hidden. After RED: these three local behaviors corrected; three assertions in the new handoff test files corrected against existing route/mapping/Boolean behavior, each explained in BUILD.md. Whole-file GREEN: renderer 9/0, routes 13/0, browser 9/0; full library 81/0, channel-proof 2/0. Approved old-test numstats remain 80/66 and 3/1. Full webui/build and full integration gate have not run. Isolated mutants next within the authorized 19:38Z hard stop; no completion claim.
2026-10-23 | Fethry | Row 225 T1 expanded evidence complete (sage-3647). Full library81/0 and channel-proof2/0; final targeted renderer9/0, routes13/0, browser9/0. Isolated kit14/14 assertion kills on507901277, every target restored/hash-checked, candidate hashes unchanged. First attempt stopped at a wait-based failure, recorded as not killed; corrected the new test's repaint wait, baseline9/0, and reran the whole kit. No processes left; lane A returned around19:29Z. Source manifest for Dewey prepared; full webui/build and Sage's integration/scripts gate still pending. Raw logs preserved in scratch, normalized display copies and raw SHA256 provenance in agent evidence.
+8
View File
@@ -47,6 +47,14 @@ its launch configuration remains fixed, including on resume. Use Filbert's
wrapper to select that configuration; a direct shared-helper invocation uses
its own supplied flags or the system defaults.
Any run that includes the webui suite, whether a seat's slot run or a landing
gate, sets `MOSAIC_REQUIRE_EDITOR_BUILD=1`, so a missing editor toolchain fails
the run instead of skipping the rebuild test. A worktree that links the
canonical `node_modules` also runs `npm ci --ignore-scripts --no-audit --no-fund`
in its own `packages/webui` first. The root install carries esbuild 0.28.1
through pi-coding-agent, and the rebuild needs the 0.28.2 that
`packages/webui/package-lock.json` pins (Vikunja 597, mos-1453).
All agents follow repository governance and current user direction. Team
leadership does not add deployment or push authority. Coordinate overlapping
work with Sage and preserve other sessions' changes.
+376
View File
@@ -0,0 +1,376 @@
# Row 225 (C2-10b) Console chat: replies, citations, write notices, status line
Owner: Morgana, from sage-3582 (18:53Z 10-23). Base: origin/refactor 45dca69f6. Branch: morgana/row225.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-10b. Design: agents/gosalyn/work/case2/DESIGN.md 7.4,
5.2 (the reply schema and the check result) and the write notice and `library.revert` (about line 286).
## Ruling on the dependency gap (sage-3591)
225 builds against the design contracts. `renderReply` stays pure, and the caller supplies the answers
it can't know. The revert route proxies to the bus verb and shows the refusal code until the verb
exists. Landing order: 225 lands after 217 (C2-06), not after 288 (C2-02). Sage adds `after 217` to
row 225. Before 288 lands, the Revert button gets the bus's refusal and shows its code, as the brief
allows.
## A1. The check result I assume (to 217, Launchpad)
The broker stores one check result with each agent-to-human message the guard passes. The Console reads
it on the message row that `/api/chat/threads/<id>/messages` returns, under the key `check`:
```json
{
"check": {
"citations": [
{ "scope": "canon", "agent": null },
{ "scope": "supporting", "agent": "acme/scribe" },
{ "scope": "web", "agent": null }
]
}
}
```
- `check` is `null` or absent on a message the guard didn't check: a human's message, a free-text reply,
a notice, or a message from before 217.
- `check.citations` has one entry per entry of the body's `citations`, in the same order.
- `scope` is one of `canon`, `playground`, `supporting` and `web`. A url cite is `web`.
- `agent` is the `Mosaic-Agent` trailer value, `<business>/<instance>`, from
`git log -1 --format=%(trailers:key=Mosaic-Agent,valueonly) <commit> -- <path>`, or `null` when the
file's last commit at `commit` carries none. A url cite has `agent: null`. The Console shows the
agent-written label when `agent` is a nonempty string, and names the agent.
The Console trusts nothing in the body for scope or label. A cite with no matching check entry renders
with no badge and no label. It doesn't fall back to a scope it worked out itself.
If 217 uses other names, I change mine to theirs before either of us pushes (sage-3591 item 1).
## A2. The revert verb I assume (to 288, Honker)
- Verb: `library.revert`. A human verb (design M4): from an agent session it refuses `human-required`,
like `library.status` (`broker.mjs:5133`).
- Args: `{ "project": <business project name>, "commit": <40 lowercase hex> }`. The design writes
`library.revert {commit}`. `project` is added to match the other library verbs, and like theirs it is the
key of `business.projects` (`mirror.mjs:31`), which is the chat project's `repo` link, not its UUID. Both are required, and no other key
is allowed (`invalid-request`), as the other library verbs check with `keys(args, ...)`.
- Result: `{ "project", "path", "reverted": <the commit given>, "commit": <the new commit> }`.
- Refusal codes the Console shows by name:
- `library-revert-not-agent`: `commit` has no `Mosaic-Agent` trailer for this business, or isn't
reachable from the served commit.
- `stale-base`: a later commit changed that path.
- `human-required`, `invalid-request`, `project-not-configured`.
- Before 288 lands, the bus refuses the verb (expected `unknown-verb`, `broker.mjs:6578`), and the Console
shows that code.
- HTTP status in the Console (`packages/webui/src/bus.mjs` `busStatus`): `stale-base` and
`library-revert-not-agent` map to 409. Every other code keeps the existing mapping.
## A3. The status line
`GET /api/library/status?project=<id>` proxies `library.status {project}` through the existing
`libraryStatus` option. `cli.mjs:110` already passes that option, but `startServer` doesn't read it.
The status line shows:
- the served commit (7 characters) and its age;
- `library-diverged` when the sync state names it;
- the fetch error code;
- the reply limits in force, and the keys that fell back;
- the count of R-problems, linking to the list.
It reads the fields `readSyncState` returns at the base (`packages/library/src/mirror.mjs:232`). It shows
any field 217 adds (the limits) only when present.
## A4. "Changed since" (ruled sage-3596)
Sage ruled that 217 supplies `last`. On a `thread.messages` row that carries `check`, each path cite's
entry gets `last`, the file's last commit at the served tip, read when the message is read. When `last`
is absent the Console marks nothing changed, and a test pins that.
One more field is needed, raised in morgana-0200. The cited `commit` is a library commit, not the
file's own last commit, so a cite made at a commit that didn't touch the file would differ from `last`
while the file is unchanged. I asked 217 to also store `file`, the file's last commit at the cited
commit. That's `%H` from the same `git log -1 <commit> -- <path>` call that reads the trailer, so it
costs no extra git call. The Console marks a cite changed when both `file` and `last` are 40-hex and
differ. The cited commit is reachable from the served one (5.2), so `file` is an ancestor of `last`,
and differing means newer. A missing or malformed `file` or `last` marks nothing.
The entry the Console reads, in full: `{ scope, agent, file?, last? }`.
## A5. `no-cited-answer` retires
`renderItems` and its `NOTICES` (`no-cited-answer`, `no-answer`, `turn-failed`) go. 217 sends the
runner's fixed texts as plain bodies (design 5.2, "Runner texts"), and renderReply shows those as free
text. Design 7.4 names no other notice than `library-write`, so nothing is lost (sage-3596 item 3).
## Files
Both deviations below were accepted in sage-3596 and are named in the manifest for Dewey. The brief owns these:
- `packages/library/src/render.mjs`: `renderReply` replaces `renderItems`.
- `packages/library/src/index.mjs`: the export.
- `packages/library/README.md`: the section.
- `packages/library/tests/render.test.mjs`.
It also owns `bus.js`, its stylesheet, `serve.mjs` (only the two routes) and new webui tests. Two
deviations, both recorded here:
- The chat view's message list is `packages/webui/src/public/chat.js:85` (`window.MosaicChat`), not
`bus.js`. The reply, notice and Revert rendering goes in `chat.js` and `chat.css`. The status line
goes in the chat thread view, so it is also in `chat.js`.
- `packages/webui/src/console-bus.mjs` and `cli.mjs` each get one `libraryRevert` line, beside the
existing `libraryStatus` wiring.
## Overlap with row 220 (C2-10a, Researcher, 3ef6da90d)
220 changes one line each in the webui tests discord.test.mjs, fixture.mjs, history-fixture.mjs,
relaunch.test.mjs and return-flow.test.mjs. Its other paths are in control-board, conversation, discord
and business. 225 doesn't touch those five files or those packages. Its webui tests are new files.
## Handoff to Fethry (mos-1501, Q245)
The row moved to Fethry (Codex, fethry/row225). Everything below is **untested**: no suite or single file
has run on it, since a run needs Sage's CLEAR. Each changed file passes `node --check`.
Done in the handoff commit:
- `render.mjs` `renderReply(body, {check, changed, name})`; index export; README section;
`render.test.mjs` rewritten. `renderItems` is gone from every package.
- `serve.mjs`: `/render.mjs` static entry (owner and user file maps), `GET /api/library/status`
(one `project` query, answer must name that project), `POST /api/library/revert` (save's boundary,
strict `{project, commit}`, refusal shows the code, lost or unusable answer is `outcome-unknown`,
never retried). Both are nulled for a granted user, and `userRoute` keeps them owner-only. The tester
listener refuses `libraryRevert`.
- `bus.mjs`: `library-revert-not-agent: 409`. That re-pins `packages/webui/src/bus.mjs` in
`packages/bus/tests/channel-proof.test.mjs`, with a comment line, as row 254 did. It's a third deviation,
in another package's test.
- `console-bus.mjs` and `cli.mjs`: `libraryRevert`, beside `libraryStatus`.
- `chat.js`: dynamic `import('/render.mjs')` (CSP is `script-src 'self'`); body text until it loads.
Cites with a scope badge, the agent-written label, "changed since" from `file`/`last`, links only for
https with `rel="noopener noreferrer"`. A notice gets a Revert button behind `confirm()`. The library
status line goes under the thread heading.
- `chat.css`: cite, badge, notice and status styles.
- New tests: `packages/webui/tests/library-routes.test.mjs` and
`packages/webui/tests/chat-replies-browser.test.mjs`. 220's five files are untouched.
Decisions made in the build:
- The library name is the chat project's `repo`. A project with no repo reads no status and its
Revert does nothing.
- These codes hide the status line: `project-not-declared`, `library-mapping-invalid`,
`project-not-configured`, `not-configured` and `owner-only`.
- Otherwise every code-repo thread, and every granted user, would show "unavailable".
- Cost: a broken library mapping also hides the line. `library.status` from the CLI still shows it.
- "Age" is the time since `lastFetch` ("fetched 5 min ago"), not the served commit's age, which the
status doesn't carry.
- The limits fields are assumed to be `replyLimits {requireCitation, citationsPerReply}` and
`replyLimitsFallback [keys]`, and are shown only when present. **Not agreed with 217.** Confirm with
Beakley and rename.
- A notice body's shape is all the renderer checks. An agent in a business without a library could
send a body that looks like a notice. The page would show it, but `library.revert` still refuses it.
Left:
1. Get CLEAR and run the webui and library suites, `channel-proof.test.mjs`, and the two new files whole.
The browser test's selectors and waits have never run.
2. Agree the limits field names with 217 (Beakley).
3. Granted-user R6 G5 (`granted-user.test.mjs:252`) lists owner verbs by hand. Add `library.revert`
once 288 lands.
4. BUILD.md: the mutant list and the mos-1275 timeout plan.
5. The manifest names the three deviations: chat.js and chat.css in place of bus.js, the console-bus and
cli lines, and the channel-proof re-pin.
## Fethry continuation (2026-10-23, Q245)
Owner: Fethry, thread 14f80e3c-ace7-4793-a577-e1275ca09b0b. Branch fethry/row225.
This file carries Morgana's complete BUILD.md above unchanged, recovered from 9a131e956
(base 45dca69f6). Morgana stopped editing 225 at that handoff. Issue #1726; Dewey reviews.
Sage owns queue moves, test slots, gate and landing. No Node/tests until Sage CLEAR
(sage-3611); commits/pushes before the gate are explicitly Untested. No credential files read.
### Confirmed interfaces and rulings
- Beakley beakley-0004 confirms ordered check.citations {scope, agent, file, last}; a missing
or malformed file/last means no changed marker. Fetched age accurately describes lastFetch.
- Beakley's concrete library.status shape is replyLimits:
{requireCitation, citationsPerReply, fallback: [keys], problem: 'reply-limits-unreadable' | null}.
The inherited top-level replyLimitsFallback assumption is superseded. Show nested fallback and
problem, with a browser regression test for each. No broker or policy change in this row.
- Bubba bubba-0002 confirms A2 and the exact broker notice shape. fethry-0005 forwards the
project-key correction: project is chat project.repo, not the chat UUID.
- Keep the inherited ordinary-code-repo/granted-user status hiding behavior for now, but a
library-mapping-invalid error should stay visible: hiding an invalid mapping hides diagnosis.
Confirm this local UI choice through regression coverage and tell Sage/Dewey.
### Continuation plan and acceptance evidence
Spec: docs/plans/2026-10-20_case2-rows.md C2-10b; design 5.2 and 7.4;
standing rulings sage-3591, sage-3596 and sage-3611. Implement inline in this existing isolated
T3 worktree; use the existing candidate and tests, never restart the implementation.
1. Static audit the handoff against the contracts and inherited tests. Add failing coverage for
nested limits fallback/problem, wrong-project revert receipts, missing file/last and invalid
mapping visibility. Existing tests require Mos approval through Sage before further edits.
2. On Sage CLEAR, run the new files whole and library renderer tests with per-test deadlines;
record RED before changing uncovered behavior. Fix only demonstrated local defects. Run
targeted GREEN, then the full relevant suites only within the authorized slot.
3. Record exact commands, timings and output paths. Prepare mutant kill list below; run only on
CLEAR, using isolated copies so the candidate is never temporarily changed for mutation.
4. Freeze a manifest containing all source/test/doc changes and all three handoff deviations.
Push only fethry/*, as additional commits. Send exact head/results to Dewey and Sage; cc Mos
on pushes/blockers. Sage performs queue moves, gate and landing. No merge/release/live changes.
### Existing test changes requiring Mos OK through Sage (mos-1272)
Diff against 45dca69f6, inherited from Morgana 9a131e956:
| File | Added | Removed | Reason |
| --- | ---: | ---: | --- |
| packages/library/tests/render.test.mjs | 80 | 66 | Brief requires retiring renderItems and replacing its tests with renderReply coverage |
| packages/bus/tests/channel-proof.test.mjs | 3 | 1 | bus.mjs gains library-revert-not-agent:409; refresh that source hash and document why |
No further edits to these files pending approval. No edits to row220's five tests.
The new webui test files are additions against the row base.
### Timeout plan (mos-1275)
Every Node test run uses --test-concurrency=1 and --test-timeout=30000, under an outer timeout.
New library/route files: outer 120s each; new browser file: outer 240s. Full library and channel-proof:
outer 180s; full webui: outer 900s, MOSAIC_REQUIRE_EDITOR_BUILD=1. Full webui requires its own
packages/webui npm ci per agents/README.md and Sage CLEAR. Output redirects to an evidence log;
inspect tail, failure names and summary, never claim green from an exit alone. A timeout is a
failed run: stop, report the exact command/phase/last output, and diagnose before retrying.
Sage's gate owns every scripts/test-*.sh; no implicit slot or test authority from this plan.
### Mutant plan (pending CLEAR)
- M1: drop a path badge or the supporting label; cite browser assertions must fail.
- M2: allow http/javascript links; renderer and browser safe-link assertions must fail.
- M3: compare cited library commit to last instead of file to last, or treat a missing file/last as
changed; changed-since assertions must fail, including same-file/different-library-commit.
- M4: ignore nested fallback/problem; status browser tests must fail.
- M5: remove a revert fence (send, identity, exact Origin or Fetch Metadata), strict key/commit
validation or wrong-project receipt validation; route tests must fail with zero unexpected calls.
- M6: retry a refused/unknown revert; single-call route/browser assertions must fail.
- M7: remove granted-user library-route fence; owner-only route coverage must fail.
Progress: handoff and interfaces recovered; candidate untested. No completion claim.
Static evidence correction: the first pushed patch artifact used ordinary context lines, whose
single-space blank context lines make git diff --check report whitespace in the artifact. Regenerated
inherited-old-tests.patch with --unified=0; it is the same exact two-file change, applicable with
git apply --unidiff-zero. No source or existing test changed for this correction.
Mutation kit authored at mutants.py (11 concrete mutations across M1-M7), Python AST parse only.
It archives the committed packages plus their tracked fixture dependencies into disposable snapshots,
links the existing dependency install read-only in practice (tests do not install), enforces exactly
one mutation anchor and requires an assertion failure for a kill. It records source hashes before
and after. It has not run; anchors for the planned fixes intentionally require those fixes first.
No Node/tests/build/browser work has run in Fethry's session pending Sage CLEAR.
### Old-test approval and preservation condition (mos-1510, sage-3623)
Mos approves render.test.mjs +80/-66 and channel-proof.test.mjs +3/-1 exactly. No additional edits
are permitted to either file without a new Mos OK through Sage. The condition on the renderer:
- The old malformed-body fixed-line behavior no longer applies. Design 5.2 now says any body
outside the cited-object schema is free text whose text is the whole body. Rejecting it into a
fixed line would hide human/free-text/older messages and the four fixed runner texts. The new
render.test.mjs tests “free text renders as itself with no citations” and “a body without the
reply or notice shape is free text: the whole body, no citations” explicitly cover this new rule.
- The old no-options path-badge behavior still applies. The new render.test.mjs test “without a
check result a path cite has no badge, no label and is never changed” covers it, and the browser
test checks an unchecked path cite has no badge/label. No path inference fallback was added.
Dewey checks both against the frozen old-test delta. The two numstats remain 80/66 and 3/1.
### Test slot and shared protocol (sage-3623, beakley-0006)
Targeted lane A slot is planned around 19:47Z, after Gosalyn's 226 run and Sage's queue gap.
Wait for explicit CLEAR; the scheduled time alone grants nothing. Hard stop 20:00Z for Sage's
226 gate. Boyd's lane B bus/cli/business work is disjoint and does not count as a busy lane A.
The three requested files are the library renderer test and the two new webui files, whole/serial.
Beakley confirms reply-protocol.mjs exports only GUARD_CODES (the seven guard refusal codes) and
FIXED_TEXTS {empty, failed, uncited, tooLong}. It has no scope/notice constants or validator.
renderReply defines none of those codes or runner texts: it already renders the fixed bodies as free
text. It therefore needs no protocol import and no extra browser module route (beakley-0006).
Do not add unused imports or redefine those shared codes/texts later.
2026-10-23T19:21:07Z dependency update (bubba-0006): Bubba's first static row288 push is
origin/bubba/row288 c867cada9, explicitly Untested with no Node run. A2 library.revert uses the
business.projects key; broker notice retains the exact agreed body. Bubba adds librarySave only
in console-bus.mjs/cli.mjs and leaves libraryRevert wiring owned by 225. Preserve both callback
changes when Sage integrates. No dependency merge or test run performed here; 225 still lands
after 217 and may show unknown-verb until 288 lands.
## T1 targeted RED to GREEN (sage-3641, 2026-10-23T19:23Z)
All three files ran whole and serial, with TMPDIR=/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2,
--test-concurrency=1 --test-timeout=30000 and outer timeout -k 10 (120s renderer, 120s routes,
240s browser). Initial renderer 9/0 (0.105s), routes 9/3 (0.226s), browser 5/3 (5.630s).
Final renderer 9/0 (0.105s), routes 12/0 (0.212s), browser 8/0 (5.284s). Logs in evidence/.
Production fixes after RED:
- Reject a library.revert receipt with the wrong project as outcome-unknown; no retry.
- Read replyLimits.fallback and display replyLimits.problem per beakley-0004/0006.
- Show library-mapping-invalid in the status line; its hidden-status branch is covered by the
browser refusal matrix once the nested-limits assertion passes. This diagnosis was made statically;
the first RED exits that matrix at the preceding fallback assertion, so visibility itself has only
a GREEN run, unlike the fallback/problem and wrong-project fixes.
Three assertion corrections in NEW handoff files (not pre-existing tests, per sage-3647):
1. library-routes.test.mjs: POST /api/library/status in preview mode expects 403, because the existing
global preview write fence runs before the GET-only route and returns read-only. The route remains
GET-only and no callback fires on that POST. Do not weaken or bypass the fence to get 405.
2. library-routes.test.mjs: project-not-declared expects 400, which busStatus already maps at the
row base. The inherited 502 expectation contradicted that existing map. No production map change.
3. chat-replies-browser.test.mjs: coerce the whole “img element OR window.injected” expression to a
Boolean. With no injected node/code the old expression returns undefined, not false. Assertions
still require neither DOM injection nor executed handler. All preceding cite/link assertions pass.
Frozen pre-existing test deltas still render.test.mjs 80/66 and channel-proof.test.mjs 3/1.
No Node/Chromium process with this slot TMPDIR remained after targeted tests (checked /proc).
Expanded CLEAR sage-3647: full library, then channel-proof whole, then isolated mutants that fit;
still hard stop 19:38Z. No full webui suite, npm ci or editor build authorized in this window.
Expanded-slot evidence: full library 81/0 (9.179s), channel-proof 2/0 (0.573s), whole/serial,
--test-timeout=30000, outer timeout -k 10 180, same TMPDIR. Logs copied to evidence/.
To exercise invalid-mapping visibility independently, added its own browser case and temporarily
restored only the hidden-mapping branch. Whole browser RED: 7/2 (8.810s); the direct assertion sees
null instead of the diagnostic, and the refusal-matrix wait also fails. Finally restored the exact
corrected source before the next run. This supersedes the earlier visibility “GREEN only” note.
Final targeted coverage before mutation: browser 9/0 (6014.970ms runner duration), routes 13/0.
Added an independent send-off test with a configured libraryRevert callback: absence of --send
still refuses with zero callback calls. This makes the send fence independently mutation-testable.
The kit now has 14 concrete mutations, including agent-label, unreadable-limits problem and send.
Each isolated target is restored to its candidate SHA256, and worktree hashes are checked before
continuing to the next mutant (sage-3647). Runner uses TAP so assertion failures can be distinguished
from timeouts/import/process failures. Full webui/build and the integration gate remain pending.
Dependency update beakley-0008: row217 first push origin/beakley/row217 c25f5b8da is UNTESTED,
continued from Launchpad e25ef0373. No dependency merge here; its protocol/module mount belongs to
217 and renderReply needs none of those codes/texts. Integration acceptance remains Sage's gate.
First isolated-mutant attempt stopped honestly at M3-wrong-commit: M1 badge, M1 agent label and M2
https-only link mutants were killed by assertions; M3 failed via the browser's expected-state wait,
so the strict runner did NOT count it as a kill. Every target restored/hash-checked and candidate
hashes unchanged. Logs retained in evidence/mutants-attempt1/. Corrected the new browser test to wait
for a completed repaint (independent of desired output), then assert changed-marker count. Whole
browser baseline remains 9/0. No production change. Re-run the whole isolated kit against this test.
## T1 complete: isolated mutants and source-review handoff
sage-3647 final kit on source candidate 507901277: 14/14 assertion kills, no timeout/import/process
failure counted. Each mutant matched one anchor, restored the target SHA256 and checked the three
candidate source hashes before the next. evidence/mutants-final/summary.json records every result;
final worktreeUnchanged=true. Logs name the failing tests, including M3's direct assertion after the
refresh wait correction. /proc inspection found no remaining Node/Chromium process with the slot
TMPDIR. Returned lane A to Sage as fethry-0016 at about19:29Z, before the19:38Z hard stop.
Checked-in TAP display copies remove trailing whitespace so the diff is clean. Their raw SHA256
values are recorded in evidence/raw-log-provenance.json; original byte-for-byte logs remain under
/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2. This changes evidence presentation only, never tests.
MANIFEST.md and manifest.r1.sha256 freeze the13 changed package files for Dewey. All three handoff
deviations and Mos1510's two preservation conditions are explicit. Old-test numstats remain80/66 and3/1.
No full webui/npm ci/build was run; Sage explicitly reserved it for a later window. Full package/script
and dependency integration gate remains required before landing. No queue move, merge or live action.
+67
View File
@@ -0,0 +1,67 @@
# Row 225 review packet — C2-10b (#1726)
Owner Fethry (14f80e3c), reviewer Dewey (84fb346c). Base 45dca69f6.
Source candidate 507901277 continues Morgana 66f7becf2 and 9a131e956; no rewritten history.
Status: targeted tests and full library green; full webui/build and Sage's full gate still pending.
This packet requests source/manifest review, not landing or a full-green claim.
## Behavior and contracts
The Console shows reply text and citations with path/line/quote, scope and agent-written labels,
marks a changed file only with valid differing check.file/check.last, links only https URLs, shows
write/delete notices and explicit Revert results, and displays library status, fetch age, divergence,
fetch errors, nested replyLimits metadata and R-problems. Invalid library mappings remain visible.
Revert validates {project,commit}, preserves refusal codes and reports uncertain/wrong-project
receipts as outcome-unknown without retry. The project is chat project.repo (business.projects key).
A1-A5 are in BUILD.md. Dependency contracts: row217 ordered check.citations and nested replyLimits;
row288 A2 revert result/notice. No dependency merge in this candidate. Landing order is 217 then225;
unknown-verb is expected until288 lands. Beakley's GUARD_CODES/FIXED_TEXTS need no renderer import:
renderReply defines neither and renders those fixed bodies as free text.
## Frozen implementation and tests
manifest.r1.sha256 freezes the 13 changed package paths, including the two approved existing-test
changes. Check from repository root with `sha256sum -c agents/fethry/work/queue-225/manifest.r1.sha256`.
All other changed files are BUILD/session records, handoff/approval evidence and this review packet.
No role, policy, schema, secret, live instance, deployment or queue change is included.
Three departures from the brief, carried from Morgana's handoff:
1. Chat rendering/status live in public/chat.js and chat.css, where the message list is, instead of
bus.js and its stylesheet (sage-3596). serve.mjs serves the same pure render.mjs module to owner
and granted-user pages; no duplicated browser renderer and no generated asset.
2. console-bus.mjs and cli.mjs wire libraryRevert beside libraryStatus (sage-3596). --send still
independently gates Revert. Preserve row288's separate librarySave wiring at integration.
3. webui/src/bus.mjs maps library-revert-not-agent to409; bus/tests/channel-proof.test.mjs re-pins
only that source's hash and documents why. Mos read and approved this exact +3/-1 (mos-1510).
Existing-test authority: Mos1510 via Sage3623 approves render.test.mjs +80/-66 and channel-proof +3/-1.
Both exact numstats are unchanged. The malformed-body fixed-line rule is superseded by design5.2's
whole-body free text, covered by two replacement renderer tests. The old no-options path-badge rule
is retained and covered by the replacement test plus unchecked-cite browser coverage. BUILD.md
explains these two preservation conditions and the three new-file assertion corrections in detail.
## Verification and remaining gate
Evidence logs are under evidence/; commands, deadlines, failures and corrections are in BUILD.md.
Targeted whole-file tests: renderer9/0, routes13/0, browser9/0. Full library81/0, channel-proof2/0.
No skips/cancellations. Invalid-mapping visibility also has an independent RED then GREEN.
All 14 isolated mutants were killed by assertions on source candidate507901277; each mutation
matched one anchor, restored its source hash, and verified unchanged candidate hashes before the next
mutation. Summary: evidence/mutants-final/summary.json. The first attempt stopped at an expected-state
wait (not counted as a kill), then the new browser test was improved and the whole kit rerun.
Raw logs remain under the scratch root; checked-in display copies trim trailing whitespace and record
raw digests in evidence/raw-log-provenance.json.
Before landing, Sage still runs full webui with MOSAIC_REQUIRE_EDITOR_BUILD=1 after the worktree-local
webui npm ci, every applicable package/script suite and integration against217. No full webui/build
in T1 (sage-3647). Dewey approval and Sage's gate/landing remain required; queue moves belong to Sage.
Static audit: the channel-proof source pin matches webui bus.mjs; no hash pin of bus.js, serve.mjs
or library render.mjs was found. packages' only remaining renderItems match is an explanatory comment
in the frozen replacement test; no runtime export, import or call remains. No no-cited-answer match.
Review focus: stored check metadata versus untrusted body, missing/malformed file/last, https-only
links and inert hostile text, Revert authority/receipt uncertainty, async route/sign-out isolation,
and status diagnostics without owner transport leakage to a granted user.
@@ -0,0 +1,64 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 704.129155
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 690.249337
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 595.036289
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 627.082447
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 787.799722
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 587.311743
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 618.209773
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 633.838649
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 619.313248
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6014.970062
@@ -0,0 +1,64 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 636.096036
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 741.239029
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 568.879998
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 641.929005
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 870.041985
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 648.87451
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 566.725897
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 611.150846
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 618.488891
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 9
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6014.364529
@@ -0,0 +1,16 @@
✔ chat replies: free text, cites with badges, the agent label, changed since and safe links (583.234335ms)
✔ chat replies: a write notice reads as its line with a Revert; a refused revert shows its code (634.71741ms)
✔ chat replies: a read-only Console shows the notice with Revert disabled (574.836149ms)
✔ chat status line: served commit and age, divergence, fetch error and the problems one click away (607.74749ms)
✔ chat status line: limits when the status carries them, no problems, and refusals (861.034669ms)
✔ chat status line: a project with no repository link reads no library status (611.433469ms)
✔ chat status line: unreadable reply limits show their problem and both fallback keys (606.380675ms)
✔ chat replies: unchanged file at a different library commit and incomplete checks stay unmarked (650.947719ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5251.735356
@@ -0,0 +1,78 @@
✖ chat replies: free text, cites with badges, the agent label, changed since and safe links (980.914799ms)
✔ chat replies: a write notice reads as its line with a Revert; a refused revert shows its code (753.988771ms)
✔ chat replies: a read-only Console shows the notice with Revert disabled (672.814814ms)
✔ chat status line: served commit and age, divergence, fetch error and the problems one click away (622.859977ms)
✖ chat status line: limits when the status carries them, no problems, and refusals (583.956229ms)
✔ chat status line: a project with no repository link reads no library status (635.758222ms)
✖ chat status line: unreadable reply limits show their problem and both fallback keys (581.636121ms)
✔ chat replies: unchanged file at a different library commit and incomplete checks stay unmarked (653.788881ms)
ℹ tests 8
ℹ suites 0
ℹ pass 5
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5597.371605
✖ failing tests:
test at packages/webui/tests/chat-replies-browser.test.mjs:81:1
✖ chat replies: free text, cites with badges, the agent label, changed since and safe links (980.914799ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ undefined
- false
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:102:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: undefined,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/chat-replies-browser.test.mjs:152:1
✖ chat status line: limits when the status carries them, no problems, and refusals (583.956229ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:156:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems',
expected: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/chat-replies-browser.test.mjs:173:1
✖ chat status line: unreadable reply limits show their problem and both fallback keys (581.636121ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:179:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · no problems',
expected: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,22 @@
TAP version 13
# Subtest: channel conversion preserves the four proof/transport byte baselines and modules cannot import a proof caller
ok 1 - channel conversion preserves the four proof/transport byte baselines and modules cannot import a proof caller
---
duration_ms: 4.280459
type: 'test'
...
# Subtest: channel recovery still requires the same shim path and rejects an absent or forged proof
ok 2 - channel recovery still requires the same shim path and rejects an absent or forged proof
---
duration_ms: 413.032186
type: 'test'
...
1..2
# tests 2
# suites 0
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 539.602759
@@ -0,0 +1,496 @@
TAP version 13
# Subtest: resolver step 1: a path from the repo root, with or without .md
ok 1 - resolver step 1: a path from the repo root, with or without .md
---
duration_ms: 1.051208
type: 'test'
...
# Subtest: resolver step 2: the same path with vaultPrefix/ stripped
ok 2 - resolver step 2: the same path with vaultPrefix/ stripped
---
duration_ms: 0.164795
type: 'test'
...
# Subtest: resolver step 3: a path relative to the linking file, never above the repo root
ok 3 - resolver step 3: a path relative to the linking file, never above the repo root
---
duration_ms: 0.192765
type: 'test'
...
# Subtest: resolver step 4: a base name that matches exactly one indexed file
ok 4 - resolver step 4: a base name that matches exactly one indexed file
---
duration_ms: 0.201611
type: 'test'
...
# Subtest: a link into another of her repos resolves to nothing
ok 5 - a link into another of her repos resolves to nothing
---
duration_ms: 0.130282
type: 'test'
...
# Subtest: wikilink forms, embeds, and links skipped inside code
ok 6 - wikilink forms, embeds, and links skipped inside code
---
duration_ms: 1.445011
type: 'test'
...
# Subtest: only a body embed whose target has an extension other than .md, in any case, is an attachment
ok 7 - only a body embed whose target has an extension other than .md, in any case, is an attachment
---
duration_ms: 0.25003
type: 'test'
...
# Subtest: the fixture mapping checks and comes back frozen
ok 8 - the fixture mapping checks and comes back frozen
---
duration_ms: 1.772928
type: 'test'
...
# Subtest: C2-01c: a prefix inside or holding another entry's book refuses library-playground-other-universe
ok 9 - C2-01c: a prefix inside or holding another entry's book refuses library-playground-other-universe
---
duration_ms: 0.735312
type: 'test'
...
# Subtest: C2-01c: a prefix equal to a book folder, or holding its own, refuses library-mapping-invalid
ok 10 - C2-01c: a prefix equal to a book folder, or holding its own, refuses library-mapping-invalid
---
duration_ms: 0.532498
type: 'test'
...
# Subtest: C2-01c: universe-level prefixes lie outside every book, in a plain universe and in a hybrid with no books
ok 11 - C2-01c: universe-level prefixes lie outside every book, in a plain universe and in a hybrid with no books
---
duration_ms: 0.668173
type: 'test'
...
# Subtest: C2-01c: a draft takes kind, name and playground only, and comes back frozen with books []
ok 12 - C2-01c: a draft takes kind, name and playground only, and comes back frozen with books []
---
duration_ms: 1.032507
type: 'test'
...
# Subtest: no playground prefix holds another, and no book folder holds another
ok 13 - no playground prefix holds another, and no book folder holds another
---
duration_ms: 0.507943
type: 'test'
...
# Subtest: shape refusals: kind, remote, branch, unknown keys, types, manuscript, folder names
ok 14 - shape refusals: kind, remote, branch, unknown keys, types, manuscript, folder names
---
duration_ms: 0.740368
type: 'test'
...
# Subtest: C2-03a sage-2021: user is optional, kept when given, and must be a plain account name
ok 15 - C2-03a sage-2021: user is optional, kept when given, and must be a plain account name
---
duration_ms: 0.659277
type: 'test'
...
# Subtest: a hybrid universe is a shape the query code can take (1.4), with sources that must exist
ok 16 - a hybrid universe is a shape the query code can take (1.4), with sources that must exist
---
duration_ms: 0.695117
type: 'test'
...
# Subtest: a caller-supplied fail receives the code, as business file loading will pass it (C2-04)
ok 17 - a caller-supplied fail receives the code, as business file loading will pass it (C2-04)
---
duration_ms: 0.392362
type: 'test'
...
# Subtest: scope comes from the path alone (design 1.2)
ok 18 - scope comes from the path alone (design 1.2)
---
duration_ms: 0.466406
type: 'test'
...
# Subtest: C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization
ok 19 - C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization
---
duration_ms: 46.645994
type: 'test'
...
# Subtest: C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths
ok 20 - C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths
---
duration_ms: 32.763959
type: 'test'
...
# Subtest: C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner
ok 21 - C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner
---
duration_ms: 109.751094
type: 'test'
...
# Subtest: C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps
ok 22 - C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps
---
duration_ms: 20.129293
type: 'test'
...
# Subtest: C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock
ok 23 - C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock
---
duration_ms: 24.021555
type: 'test'
...
# Subtest: C2-04 state: missing state is empty only while the served ref is absent
ok 24 - C2-04 state: missing state is empty only while the served ref is absent
---
duration_ms: 37.436349
type: 'test'
...
# Subtest: C2-04 state: valid problems and sync metadata return no content and change no state bytes
ok 25 - C2-04 state: valid problems and sync metadata return no content and change no state bytes
---
duration_ms: 37.681315
type: 'test'
...
# Subtest: C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed
ok 26 - C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed
---
duration_ms: 105.483341
type: 'test'
...
# Subtest: C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement
ok 27 - C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement
---
duration_ms: 29.562786
type: 'test'
...
# Subtest: C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text
ok 28 - C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text
---
duration_ms: 74.057232
type: 'test'
...
# Subtest: C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies
ok 29 - C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies
---
duration_ms: 80.573844
type: 'test'
...
# Subtest: C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes
ok 30 - C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes
---
duration_ms: 253.247948
type: 'test'
...
# Subtest: C2-04 lock: same-process waits, timeout code, callback expiry and throw release
ok 31 - C2-04 lock: same-process waits, timeout code, callback expiry and throw release
---
duration_ms: 5031.462795
type: 'test'
...
# Subtest: C2-04 lock: owner death releases the kernel lock without replacing its file
ok 32 - C2-04 lock: owner death releases the kernel lock without replacing its file
---
duration_ms: 61.533295
type: 'test'
...
# Subtest: C2-04 lock: a missing flock binary becomes a handled refusal
ok 33 - C2-04 lock: a missing flock binary becomes a handled refusal
---
duration_ms: 57.176644
type: 'test'
...
# Subtest: C2-04 lock: an unawaited scoped read drains before the lock is released
ok 34 - C2-04 lock: an unawaited scoped read drains before the lock is released
---
duration_ms: 22.766874
type: 'test'
...
# Subtest: the fixture reads clean: every resolver step, appears_in and relationships wikilinks indexed
ok 35 - the fixture reads clean: every resolver step, appears_in and relationships wikilinks indexed
---
duration_ms: 33.888061
type: 'test'
...
# Subtest: R1: executables, symlinks, gitlinks, blobs over 1 MiB and non-UTF-8 text are not indexed
ok 36 - R1: executables, symlinks, gitlinks, blobs over 1 MiB and non-UTF-8 text are not indexed
---
duration_ms: 68.951498
type: 'test'
...
# Subtest: R2: unmapped paths are reported and not indexed
ok 37 - R2: unmapped paths are reported and not indexed
---
duration_ms: 25.70771
type: 'test'
...
# Subtest: R3: front matter not fully parsed stays raw, with the line where parsing stopped
ok 38 - R3: front matter not fully parsed stays raw, with the line where parsing stopped
---
duration_ms: 26.059427
type: 'test'
...
# Subtest: front matter forms the reader accepts and refuses
ok 39 - front matter forms the reader accepts and refuses
---
duration_ms: 0.448293
type: 'test'
...
# Subtest: R4: a link to no file, to more than one, or into another repo is reported and not followed
ok 40 - R4: a link to no file, to more than one, or into another repo is reported and not followed
---
duration_ms: 26.225692
type: 'test'
...
# Subtest: a block list link carries its item line, a scalar or flow link its key line, and problems follow
ok 41 - a block list link carries its item line, a scalar or flow link its key line, and problems follow
---
duration_ms: 22.565103
type: 'test'
...
# Subtest: an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves
ok 42 - an unresolved attachment embed gives no problem; a link or front matter value to one is R4; a note embed resolves
---
duration_ms: 20.903777
type: 'test'
...
# Subtest: an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed
ok 43 - an indexed Dr.Quinn.md resolves as a body link, a block list link and an embed
---
duration_ms: 25.42747
type: 'test'
...
# Subtest: R5: a canon file that refers into the playground is reported, and the link still goes by path
ok 44 - R5: a canon file that refers into the playground is reported, and the link still goes by path
---
duration_ms: 27.37119
type: 'test'
...
# Subtest: R6: a canon file that refers into another universe is reported; a hybrid may refer into its sources
ok 45 - R6: a canon file that refers into another universe is reported; a hybrid may refer into its sources
---
duration_ms: 30.585458
type: 'test'
...
# Subtest: R7: two paths that differ only in case are both left out
ok 46 - R7: two paths that differ only in case are both left out
---
duration_ms: 40.709509
type: 'test'
...
# Subtest: the reader reads the commit, never the working tree, and refuses a bad commit id
ok 47 - the reader reads the commit, never the working tree, and refuses a bad commit id
---
duration_ms: 33.579454
type: 'test'
...
# Subtest: a bare mirror reads the same as the clone
ok 48 - a bare mirror reads the same as the clone
---
duration_ms: 38.368686
type: 'test'
...
# Subtest: free text renders as itself with no citations
ok 49 - free text renders as itself with no citations
---
duration_ms: 1.571777
type: 'test'
...
# Subtest: a cited reply renders its text, then each cite with the badge from the check result
ok 50 - a cited reply renders its text, then each cite with the badge from the check result
---
duration_ms: 0.537429
type: 'test'
...
# Subtest: a parsed body renders as its string does
ok 51 - a parsed body renders as its string does
---
duration_ms: 0.304068
type: 'test'
...
# Subtest: without a check result a path cite has no badge, no label and is never changed
ok 52 - without a check result a path cite has no badge, no label and is never changed
---
duration_ms: 0.184363
type: 'test'
...
# Subtest: the label and the badge come from the check result only, never from a bad entry
ok 53 - the label and the badge come from the check result only, never from a bad entry
---
duration_ms: 0.180581
type: 'test'
...
# Subtest: a web cite is a link only for https
ok 54 - a web cite is a link only for https
---
duration_ms: 0.293669
type: 'test'
...
# Subtest: a write notice renders its fixed line
ok 55 - a write notice renders its fixed line
---
duration_ms: 0.277197
type: 'test'
...
# Subtest: a body without the reply or notice shape is free text: the whole body, no citations
ok 56 - a body without the reply or notice shape is free text: the whole body, no citations
---
duration_ms: 0.345807
type: 'test'
...
# Subtest: an empty citations list is a reply with no cites
ok 57 - an empty citations list is a reply with no cites
---
duration_ms: 0.187354
type: 'test'
...
# Subtest: C2-01c: each level gives its universe, and its book or null
ok 58 - C2-01c: each level gives its universe, and its book or null
---
duration_ms: 3.363132
type: 'test'
...
# Subtest: C2-01c: prefixes come first: in a book that holds a prefix, a file outside it is canon and one under it playground
ok 59 - C2-01c: prefixes come first: in a book that holds a prefix, a file outside it is canon and one under it playground
---
duration_ms: 0.480483
type: 'test'
...
# Subtest: C2-01c: a canon file linking into a draft gives R6, and into its own universe-level prefix gives R5
ok 60 - C2-01c: a canon file linking into a draft gives R6, and into its own universe-level prefix gives R5
---
duration_ms: 44.606565
type: 'test'
...
# Subtest: the survey prints folder names, counts, key sets and link-form counts, and never text
ok 61 - the survey prints folder names, counts, key sets and link-form counts, and never text
---
duration_ms: 37.28555
type: 'test'
...
# Subtest: folder names print DEL, C1 and bidi controls, U+061C among them, as escapes
ok 62 - folder names print DEL, C1 and bidi controls, U+061C among them, as escapes
---
duration_ms: 26.222951
type: 'test'
...
# Subtest: an embed of an attachment counts as an embed and as an attachment; a plain link to one does not
ok 63 - an embed of an attachment counts as an embed and as an attachment; a plain link to one does not
---
duration_ms: 26.23793
type: 'test'
...
# Subtest: with --mapping the survey adds scope, resolver step and problem counts
ok 64 - with --mapping the survey adds scope, resolver step and problem counts
---
duration_ms: 41.437066
type: 'test'
...
# Subtest: survey exit codes: usage 4, bad mapping 3, unreadable repo 2
ok 65 - survey exit codes: usage 4, bad mapping 3, unreadable repo 2
---
duration_ms: 17.463059
type: 'test'
...
# Subtest: scripts/mosaic library survey reaches the survey
ok 66 - scripts/mosaic library survey reaches the survey
---
duration_ms: 77.634947
type: 'test'
...
# Subtest: C2-03a argv: the fixed sequence, one https allow, no other protocol key
ok 67 - C2-03a argv: the fixed sequence, one https allow, no other protocol key
---
duration_ms: 1.605597
type: 'test'
...
# Subtest: C2-03a argv: only an https URL with a plain host, no userinfo, and a plain user
ok 68 - C2-03a argv: only an https URL with a plain host, no userinfo, and a plain user
---
duration_ms: 0.713147
type: 'test'
...
# Subtest: C2-03a helper: answers only the exact prompts for the configured host and user (dewey-0765)
ok 69 - C2-03a helper: answers only the exact prompts for the configured host and user (dewey-0765)
---
duration_ms: 31.590474
type: 'test'
...
# Subtest: C2-03a first sync: served is the fetched tip, HEAD is served, the survey reads it, and the token stays in the slot
ok 70 - C2-03a first sync: served is the fetched tip, HEAD is served, the survey reads it, and the token stays in the slot
---
duration_ms: 135.075159
type: 'test'
...
# Subtest: C2-03a fast-forward: served moves, problems are rebuilt, and a repeat changes only lastFetch
ok 71 - C2-03a fast-forward: served moves, problems are rebuilt, and a repeat changes only lastFetch
---
duration_ms: 225.707972
type: 'test'
...
# Subtest: C2-03a Filbert N2: a read that took served before a sync still finds its objects after served moves
ok 72 - C2-03a Filbert N2: a read that took served before a sync still finds its objects after served moves
---
duration_ms: 205.10431
type: 'test'
...
# Subtest: C2-03a overlap: a reader during a sync sees the old pair or the new one, never a mix
ok 73 - C2-03a overlap: a reader during a sync sees the old pair or the new one, never a mix
---
duration_ms: 175.907929
type: 'test'
...
# Subtest: C2-03a diverged: a non-fast-forward keeps serving and holds until a human accepts the tip
ok 74 - C2-03a diverged: a non-fast-forward keeps serving and holds until a human accepts the tip
---
duration_ms: 431.553812
type: 'test'
...
# Subtest: C2-03a accept starts the record over, so a changed branch gets a fresh state
ok 75 - C2-03a accept starts the record over, so a changed branch gets a fresh state
---
duration_ms: 195.011432
type: 'test'
...
# Subtest: C2-03a fetch failure: a wrong token keeps serving and records the error, lastFetch unchanged
ok 76 - C2-03a fetch failure: a wrong token keeps serving and records the error, lastFetch unchanged
---
duration_ms: 192.898644
type: 'test'
...
# Subtest: C2-03a a failed first fetch records the error with nothing served
ok 77 - C2-03a a failed first fetch records the error with nothing served
---
duration_ms: 61.594695
type: 'test'
...
# Subtest: C2-03a a 302 to another host fails the fetch and the other host sees nothing
ok 78 - C2-03a a 302 to another host fails the fetch and the other host sees nothing
---
duration_ms: 64.82991
type: 'test'
...
# Subtest: C2-03a the slot: missing or unsafe refuses before git, and sync never opens it
ok 79 - C2-03a the slot: missing or unsafe refuses before git, and sync never opens it
---
duration_ms: 59.167725
type: 'test'
...
# Subtest: C2-03a sage-2021: an entry with no user refuses library-remote-invalid and runs no git
ok 80 - C2-03a sage-2021: an entry with no user refuses library-remote-invalid and runs no git
---
duration_ms: 35.37205
type: 'test'
...
# Subtest: C2-03a one sync per mirror: an overlapping call refuses library-sync-busy
ok 81 - C2-03a one sync per mirror: an overlapping call refuses library-sync-busy
---
duration_ms: 118.260687
type: 'test'
...
1..81
# tests 81
# suites 0
# pass 81
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 9149.325891
@@ -0,0 +1,48 @@
✔ chat replies: free text, cites with badges, the agent label, changed since and safe links (633.807074ms)
✔ chat replies: a write notice reads as its line with a Revert; a refused revert shows its code (621.95095ms)
✔ chat replies: a read-only Console shows the notice with Revert disabled (577.638161ms)
✔ chat status line: served commit and age, divergence, fetch error and the problems one click away (555.847899ms)
✖ chat status line: limits when the status carries them, no problems, and refusals (3725.696272ms)
✔ chat status line: a project with no repository link reads no library status (657.572856ms)
✔ chat status line: unreadable reply limits show their problem and both fallback keys (663.163445ms)
✔ chat replies: unchanged file at a different library commit and incomplete checks stay unmarked (616.184063ms)
✖ chat status line: an invalid library mapping remains visible for diagnosis (597.976623ms)
ℹ tests 9
ℹ suites 0
ℹ pass 7
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 8782.679569
✖ failing tests:
test at packages/webui/tests/chat-replies-browser.test.mjs:152:1
✖ chat status line: limits when the status carries them, no problems, and refusals (3725.696272ms)
Error: Uncaught (in promise) Error: window.__statusReads.length && document.querySelector(".chat-conversation") && document.querySelector(".chat-library")?.textContent==="Library status unavailable (library-mapping-invalid).": undefined
at Object.evaluate (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/browser.mjs:49:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:162:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
test at packages/webui/tests/chat-replies-browser.test.mjs:200:1
✖ chat status line: an invalid library mapping remains visible for diagnosis (597.976623ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ null
- 'Library status unavailable (library-mapping-invalid).'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/chat-replies-browser.test.mjs:203:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: null,
expected: 'Library status unavailable (library-mapping-invalid).',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,4 @@
{"mutant": "M1-badges", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-badges.tap"}
{"mutant": "M1-agent-label", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-agent-label.tap"}
{"mutant": "M2-http-links", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M2-http-links.tap"}
{"mutant": "M3-wrong-commit", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": false, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M3-wrong-commit.tap"}
@@ -0,0 +1,192 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 627.180675
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
{
+ agent: null,
- agent: 'agent-written: acme/scribe',
badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: ~
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 778.572201
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 641.728604
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 629.327348
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 837.886073
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 613.037186
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 651.026507
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 735.546636
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 712.174542
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6353.090574
@@ -0,0 +1,206 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 638.589537
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
+ badge: null,
- badge: 'canon',
changed: false,
href: null,
quote: 'her grey eyes',
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa',
rel: null
...
agent: null,
+ badge: null,
- badge: 'playground',
changed: true,
href: null,
quote: 'a storm',
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa',
rel: null
...
agent: 'agent-written: acme/scribe',
+ badge: null,
- badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
rel: null
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: ~
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: ~
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: ~
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 770.070531
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 519.328288
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 647.667301
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 999.237447
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 676.088845
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 634.472637
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 655.963182
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 578.819646
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6239.022812
@@ -0,0 +1,204 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 655.901619
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
changed: false,
+ href: 'http://example.invalid/plain',
- href: null,
quote: 'plain http',
ref: 'http://example.invalid/plain',
+ rel: 'noopener noreferrer'
- rel: null
},
{
agent: null,
badge: 'web',
changed: false,
+ href: 'javascript:window.injected=2',
- href: null,
quote: 'script',
ref: 'javascript:window.injected=2',
+ rel: 'noopener noreferrer'
- rel: null
}
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: 'http://example.invalid/plain'
rel: 'noopener noreferrer'
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: 'javascript:window.injected=2'
rel: 'noopener noreferrer'
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 741.122544
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 616.728572
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 625.818493
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 867.033773
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 655.587703
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 615.997516
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 648.263681
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 624.323397
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6168.633364
@@ -0,0 +1,74 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 640.32042
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 694.233373
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 611.500508
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 606.581119
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 896.462474
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 666.798177
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 629.105559
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
not ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 3654.057789
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:184:1'
failureType: 'testCodeFailure'
error: 'Uncaught (in promise) Error: document.querySelectorAll(".chat-changed").length===0: undefined'
code: 'ERR_TEST_FAILURE'
stack: |-
Object.evaluate (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M3-wrong-commit/packages/webui/tests/browser.mjs:49:35)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-9ui_vkk0/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:195:3)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 656.44095
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 9179.328916
@@ -0,0 +1,47 @@
{
"head": "77172ce3b46eb289409068518b7022fc7be47ebe",
"clear": "sage-3647",
"worktreeUnchanged": true,
"results": [
{
"mutant": "M1-badges",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-badges.tap"
},
{
"mutant": "M1-agent-label",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M1-agent-label.tap"
},
{
"mutant": "M2-http-links",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M2-http-links.tap"
},
{
"mutant": "M3-wrong-commit",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": false,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants/M3-wrong-commit.tap"
}
]
}
@@ -0,0 +1,14 @@
{"mutant": "M1-badges", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-badges.tap"}
{"mutant": "M1-agent-label", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-agent-label.tap"}
{"mutant": "M2-http-links", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M2-http-links.tap"}
{"mutant": "M3-wrong-commit", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-wrong-commit.tap"}
{"mutant": "M3-missing-file", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-missing-file.tap"}
{"mutant": "M4-fallback", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-fallback.tap"}
{"mutant": "M4-problem", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/chat-replies-browser.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-problem.tap"}
{"mutant": "M5-send", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-send.tap"}
{"mutant": "M5-project-receipt", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-project-receipt.tap"}
{"mutant": "M5-commit-input", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-commit-input.tap"}
{"mutant": "M5-origin", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-origin.tap"}
{"mutant": "M5-fetch-metadata", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-fetch-metadata.tap"}
{"mutant": "M6-duplicate-call", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M6-duplicate-call.tap"}
{"mutant": "M7-user-fence", "anchorHits": 1, "restored": true, "worktreeUnchanged": true, "exit": 1, "killed": true, "test": "packages/webui/tests/library-routes.test.mjs", "log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M7-user-fence.tap"}
@@ -0,0 +1,192 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 646.851404
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
{
+ agent: null,
- agent: 'agent-written: acme/scribe',
badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: ~
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-agent-label/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 667.90945
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 588.346681
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 485.882289
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 709.847041
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 544.885551
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 514.410016
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 542.746154
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 582.608663
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5409.980177
@@ -0,0 +1,206 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 621.562133
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
+ badge: null,
- badge: 'canon',
changed: false,
href: null,
quote: 'her grey eyes',
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa',
rel: null
...
agent: null,
+ badge: null,
- badge: 'playground',
changed: true,
href: null,
quote: 'a storm',
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa',
rel: null
...
agent: 'agent-written: acme/scribe',
+ badge: null,
- badge: 'supporting',
changed: false,
href: null,
quote: 'green eyes',
ref: 'Notes/Eyes.md:9-10 @aaaaaaa',
rel: null
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: ~
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: ~
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: ~
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M1-badges/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 731.101767
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 633.163696
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 637.761161
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 847.929653
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 635.512979
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 645.427688
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 672.272209
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 629.043251
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6169.954716
@@ -0,0 +1,204 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
not ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 582.248796
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:81:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
{
agent: null,
badge: 'canon',
changed: false,
...
changed: false,
+ href: 'http://example.invalid/plain',
- href: null,
quote: 'plain http',
ref: 'http://example.invalid/plain',
+ rel: 'noopener noreferrer'
- rel: null
},
{
agent: null,
badge: 'web',
changed: false,
+ href: 'javascript:window.injected=2',
- href: null,
quote: 'script',
ref: 'javascript:window.injected=2',
+ rel: 'noopener noreferrer'
- rel: null
}
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: ~
rel: ~
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: ~
rel: ~
quote: 'script'
agent: ~
changed: false
actual:
0:
badge: 'canon'
ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa'
href: ~
rel: ~
quote: 'her grey eyes'
agent: ~
changed: false
1:
badge: 'playground'
ref: 'Book One/Playground/What If.md:5-6 @aaaaaaa'
href: ~
rel: ~
quote: 'a storm'
agent: ~
changed: true
2:
badge: 'supporting'
ref: 'Notes/Eyes.md:9-10 @aaaaaaa'
href: ~
rel: ~
quote: 'green eyes'
agent: 'agent-written: acme/scribe'
changed: false
3:
badge: 'web'
ref: 'https://example.invalid/tides'
href: 'https://example.invalid/tides'
rel: 'noopener noreferrer'
quote: 'spring tides'
agent: ~
changed: false
4:
badge: 'web'
ref: 'http://example.invalid/plain'
href: 'http://example.invalid/plain'
rel: 'noopener noreferrer'
quote: 'plain http'
agent: ~
changed: false
5:
badge: 'web'
ref: 'javascript:window.injected=2'
href: 'javascript:window.injected=2'
rel: 'noopener noreferrer'
quote: 'script'
agent: ~
changed: false
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M2-http-links/packages/webui/tests/chat-replies-browser.test.mjs:90:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 640.769563
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 572.506418
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 519.031091
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 743.513043
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 676.282756
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 595.166769
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 626.475981
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 596.828198
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5658.889854
@@ -0,0 +1,81 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 639.027823
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 693.772757
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 683.094789
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 637.029355
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 772.100011
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 599.342035
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 574.320042
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
not ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 621.466015
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-missing-file/packages/webui/tests/chat-replies-browser.test.mjs:184:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-missing-file/packages/webui/tests/chat-replies-browser.test.mjs:196:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 590.165121
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5921.651346
@@ -0,0 +1,81 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 704.141588
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 816.229452
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 697.826745
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 638.579753
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 850.914117
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 634.004327
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 585.469632
type: 'test'
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
not ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 673.386968
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:184:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M3-wrong-commit/packages/webui/tests/chat-replies-browser.test.mjs:196:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 579.53998
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 6292.579267
@@ -0,0 +1,102 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 683.334532
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 686.685624
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 612.757087
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 584.651483
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
not ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 637.405759
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:152:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · limits fell back: citationsPerReply · no problems'
actual: 'Library at ccccccc · fetched 5 min ago · citations required, up to 4 per reply · no problems'
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:156:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 626.790802
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
not ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 643.680732
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:173:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · reply-limits-unreadable · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
actual: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · reply-limits-unreadable · no problems'
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-fallback/packages/webui/tests/chat-replies-browser.test.mjs:179:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 645.032205
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 616.242947
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 7
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5845.226677
@@ -0,0 +1,83 @@
TAP version 13
# Subtest: chat replies: free text, cites with badges, the agent label, changed since and safe links
ok 1 - chat replies: free text, cites with badges, the agent label, changed since and safe links
---
duration_ms: 663.005514
type: 'test'
...
# Subtest: chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
ok 2 - chat replies: a write notice reads as its line with a Revert; a refused revert shows its code
---
duration_ms: 662.360161
type: 'test'
...
# Subtest: chat replies: a read-only Console shows the notice with Revert disabled
ok 3 - chat replies: a read-only Console shows the notice with Revert disabled
---
duration_ms: 620.68694
type: 'test'
...
# Subtest: chat status line: served commit and age, divergence, fetch error and the problems one click away
ok 4 - chat status line: served commit and age, divergence, fetch error and the problems one click away
---
duration_ms: 744.825035
type: 'test'
...
# Subtest: chat status line: limits when the status carries them, no problems, and refusals
ok 5 - chat status line: limits when the status carries them, no problems, and refusals
---
duration_ms: 826.418848
type: 'test'
...
# Subtest: chat status line: a project with no repository link reads no library status
ok 6 - chat status line: a project with no repository link reads no library status
---
duration_ms: 549.365993
type: 'test'
...
# Subtest: chat status line: unreadable reply limits show their problem and both fallback keys
not ok 7 - chat status line: unreadable reply limits show their problem and both fallback keys
---
duration_ms: 589.776762
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-problem/packages/webui/tests/chat-replies-browser.test.mjs:173:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
+ actual - expected
+ 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · no problems'
- 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · reply-limits-unreadable · no problems'
actual: 'Library at ccccccc · fetched 5 min ago · citations optional, up to 8 per reply · limits fell back: requireCitation, citationsPerReply · no problems'
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M4-problem/packages/webui/tests/chat-replies-browser.test.mjs:179:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
ok 8 - chat replies: unchanged file at a different library commit and incomplete checks stay unmarked
---
duration_ms: 590.919434
type: 'test'
...
# Subtest: chat status line: an invalid library mapping remains visible for diagnosis
ok 9 - chat status line: an invalid library mapping remains visible for diagnosis
---
duration_ms: 545.326381
type: 'test'
...
1..9
# tests 9
# suites 0
# pass 8
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5914.325706
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 16.128237
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 5.987607
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
not ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 4.805637
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-commit-input/packages/webui/tests/library-routes.test.mjs:64:1'
failureType: 'testCodeFailure'
error: |-
{"project":"books","commit":"abc"}
200 !== 400
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 400
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-commit-input/packages/webui/tests/library-routes.test.mjs:69:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.383811
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.000988
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.487122
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 4.9292
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.266317
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.443059
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.048018
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.864934
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.064626
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.803048
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 153.3549
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 20.271342
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
not ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 5.700968
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-fetch-metadata/packages/webui/tests/library-routes.test.mjs:48:1'
failureType: 'testCodeFailure'
error: |-
sec-fetch-site
200 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-fetch-metadata/packages/webui/tests/library-routes.test.mjs:57:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.427935
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 7.39929
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 5.425467
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.634037
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.727409
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.317954
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.583799
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.207375
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.956772
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.147745
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.819337
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 166.134058
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 16.249491
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
not ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 5.463985
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-origin/packages/webui/tests/library-routes.test.mjs:48:1'
failureType: 'testCodeFailure'
error: |-
origin
200 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-origin/packages/webui/tests/library-routes.test.mjs:57:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.035196
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 5.621744
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.928333
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.455282
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 4.838747
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.235209
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.436399
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.100574
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.943474
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.176957
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.861834
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 150.969641
@@ -0,0 +1,105 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.918199
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.26916
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 5.352342
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.134713
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.288914
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.745385
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.739155
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.264793
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.471965
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.045204
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
not ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.531252
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-project-receipt/packages/webui/tests/library-routes.test.mjs:145:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
200 !== 503
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 503
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-project-receipt/packages/webui/tests/library-routes.test.mjs:148:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.60161
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.816068
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 171.289003
@@ -0,0 +1,106 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.930067
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.470645
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.646929
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.637697
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.174441
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.513131
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.145692
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.182362
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.471806
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.064814
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 0.910004
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.16191
type: 'test'
...
# 2026-10-23T19:28:51.488Z library-revert user="jason" project="books" commit="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" commit=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
# Subtest: a configured revert callback remains unreachable when send is off
not ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 1.624909
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-send/packages/webui/tests/library-routes.test.mjs:175:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
200 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 200
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M5-send/packages/webui/tests/library-routes.test.mjs:181:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
1..13
# tests 13
# suites 0
# pass 12
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 165.919088
@@ -0,0 +1,177 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
not ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 17.922489
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:40:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
{
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
project: 'books'
},
+ {
+ commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ project: 'books'
+ }
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
actual:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
1:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:43:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.053366
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 5.869486
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.852952
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
not ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 2.870452
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:88:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
2 !== 1
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 1
actual: 2
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:96:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.904694
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 5.7962
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.518332
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.563294
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.17446
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
not ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.258633
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:145:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
{
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
project: 'books'
},
+ {
+ commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ project: 'books'
+ }
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
actual:
0:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
1:
project: 'books'
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M6-duplicate-call/packages/webui/tests/library-routes.test.mjs:151:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.362877
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 0.848309
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 10
# fail 3
# cancelled 0
# skipped 0
# todo 0
# duration_ms 169.162917
@@ -0,0 +1,126 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.215173
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 6.416847
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 5.529629
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.92431
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 4.588509
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.817257
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 6.020444
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 1.377224
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
not ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 1.015984
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:132:1'
failureType: 'testCodeFailure'
error: |-
/api/library/status
true !== false
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: false
actual: true
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:133:106)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.processPendingSubtests (node:internal/test_runner/test:974:18)
Test.postRun (node:internal/test_runner/test:1542:19)
Test.run (node:internal/test_runner/test:1467:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.695555
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.378325
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
not ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.178045
type: 'test'
location: '/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:155:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
503 !== 403
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 403
actual: 503
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mosaic-row225-mutants-e6t705gk/M7-user-fence/packages/webui/tests/library-routes.test.mjs:167:12)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 1.057394
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 11
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 176.864112
@@ -0,0 +1,147 @@
{
"head": "50790127716002f97154800ecbfb968690a9d644",
"clear": "sage-3647",
"worktreeUnchanged": true,
"results": [
{
"mutant": "M1-badges",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-badges.tap"
},
{
"mutant": "M1-agent-label",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M1-agent-label.tap"
},
{
"mutant": "M2-http-links",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M2-http-links.tap"
},
{
"mutant": "M3-wrong-commit",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-wrong-commit.tap"
},
{
"mutant": "M3-missing-file",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M3-missing-file.tap"
},
{
"mutant": "M4-fallback",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-fallback.tap"
},
{
"mutant": "M4-problem",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/chat-replies-browser.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M4-problem.tap"
},
{
"mutant": "M5-send",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-send.tap"
},
{
"mutant": "M5-project-receipt",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-project-receipt.tap"
},
{
"mutant": "M5-commit-input",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-commit-input.tap"
},
{
"mutant": "M5-origin",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-origin.tap"
},
{
"mutant": "M5-fetch-metadata",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M5-fetch-metadata.tap"
},
{
"mutant": "M6-duplicate-call",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M6-duplicate-call.tap"
},
{
"mutant": "M7-user-fence",
"anchorHits": 1,
"restored": true,
"worktreeUnchanged": true,
"exit": 1,
"killed": true,
"test": "packages/webui/tests/library-routes.test.mjs",
"log": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2/mutants-final/M7-user-fence.tap"
}
]
}
@@ -0,0 +1,155 @@
{
"rawRoot": "/mnt/storage/scratch/tmp/fethry-row225-KEmw8wQ2",
"logs": [
{
"path": "evidence/browser-final.tap",
"rawSha256": "4e6d5874786aa5d407b59d0b701652d419d0fecda5402ede40f52b313def12b0",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/browser-final2.tap",
"rawSha256": "194b3150e59868aab023d9ccfc75c69b82e43afba38c1b12e87a1fb3d68fe821",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/browser-green.tap",
"rawSha256": "030d7a5c9af70d4a7ade177906fb971b29fa4cf1fc5efd39f35643ed1969cd01",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/browser-red.tap",
"rawSha256": "db00bee89b4353efe7d324b17417347b30964f79ad4469a5dc98c6d125691f55",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/channel-proof.tap",
"rawSha256": "cb7c0398af2a2a0e7cae24ee2c8167601cbf7e3f9cf3ed7479b2a6ef65f5da8a",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/library-full.tap",
"rawSha256": "5447e3fce497a0dfb03ddd3eb2795dc2a179554f794e4c37458219c70736a645",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mapping-red.tap",
"rawSha256": "8ca7310e46df2d35fa96e3aba378721ad388dce41f003ee133b684eeb7eaf245",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M1-agent-label.tap",
"rawSha256": "db7ab1c64bc05b719e5164ed4ce1dd8b79ad53309d9b8e3e1ca0cc8867bca70f",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M1-badges.tap",
"rawSha256": "08351b9061351812008ebd7067a9aed24a9e5386d8f6284fb44ae0f59c8f2eca",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M2-http-links.tap",
"rawSha256": "69921707c219d19fb2c33a3995268b6442d783ec702050b32f78d8d2db6fb581",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-attempt1/M3-wrong-commit.tap",
"rawSha256": "fe9fb027efae26799e2dfac539e0986402cefe806ca31479f52420e67a8df423",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M1-agent-label.tap",
"rawSha256": "afbab60cbc12348c4d0c4be1a8c4590dd157c71f9ffc7375bbde82facb9868bb",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M1-badges.tap",
"rawSha256": "3bb9fb9dcf74a398934d6de48c2ca99ab2eab618fb944297b3d7db00eec0e69f",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M2-http-links.tap",
"rawSha256": "8909921fcf602fcafa94edd19c1167a8f91ed970a3e649110e3503836690e567",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M3-missing-file.tap",
"rawSha256": "3fd2749ea5bc4245f7d49f585b82eae60f61ed150ab4dd22e8148a1e9467c8c7",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M3-wrong-commit.tap",
"rawSha256": "85696c155f0a2844ee903c8fb38cab0a0fb6b0610c8b9ee8aa77a08cb6012ca2",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M4-fallback.tap",
"rawSha256": "6f59f2845e0130d4d2fe1cb4aa7a435502f6043e11ed4efd67339c12e986103a",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M4-problem.tap",
"rawSha256": "61ab08b823eb08cbb9c9eeaac82b297ea5d1272bb5dc64e9598734688b55eb80",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-commit-input.tap",
"rawSha256": "b703ee5ca47e5c64749aab3edfb5ae2e085367921ffa75337f4f0e5c7b3dc404",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-fetch-metadata.tap",
"rawSha256": "6951725340e4d74764552e7e406e0fc4bafeec35172de56d13bca852b92daa1a",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-origin.tap",
"rawSha256": "95fcb5a92e82fa7de2972fd8f78c4f0f6d1c5f49885123e01fb1b69c3b62ba21",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-project-receipt.tap",
"rawSha256": "a86806a189ef73f61ddd88479336321964eadef0793c7b45da6573ccd3e38801",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M5-send.tap",
"rawSha256": "4cce523356a3e2041197156412eb35088f8b6d38fcceca2d9b76b6098ad17627",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M6-duplicate-call.tap",
"rawSha256": "6dda962389fbc7379ac7e7bc79cc24b68db00d4f9bb649ab73e75eaa87a5da00",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/mutants-final/M7-user-fence.tap",
"rawSha256": "abec36c65c3ece6e7d0c4dfd41eede62c9a585f07b3ed4cb3b8d05f0dbabaed3",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/render-green.tap",
"rawSha256": "98463ac3d1262f84758def8bbdc27c46fa7c674bb8e8c20b9c752083163d0864",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/render-red.tap",
"rawSha256": "03645c7a5ba0c3d285e58e1d6fd05dc58a1174b463a258b01bcf258b2141a85e",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/routes-final.tap",
"rawSha256": "f2b58a97c301dc9e57458d82785bd50debb2254df5d411d8e8c4e6b9c0aa00d2",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/routes-green.tap",
"rawSha256": "c34451a0eef5f82fe6ddcbbf370179d8e8a14e36168175bdeff5b32e2072e9d6",
"normalization": "trailing whitespace removed from display copy"
},
{
"path": "evidence/routes-red.tap",
"rawSha256": "5ca5bafe2465209128508e5459f4826c34d00de86d1e79671196dc4e5c57f874",
"normalization": "trailing whitespace removed from display copy"
}
]
}
@@ -0,0 +1,17 @@
✔ free text renders as itself with no citations (1.332106ms)
✔ a cited reply renders its text, then each cite with the badge from the check result (0.493062ms)
✔ a parsed body renders as its string does (0.2706ms)
✔ without a check result a path cite has no badge, no label and is never changed (0.175324ms)
✔ the label and the badge come from the check result only, never from a bad entry (0.171275ms)
✔ a web cite is a link only for https (0.254363ms)
✔ a write notice renders its fixed line (0.201038ms)
✔ a body without the reply or notice shape is free text: the whole body, no citations (0.315846ms)
✔ an empty citations list is a reply with no cites (0.167552ms)
ℹ tests 9
ℹ suites 0
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.188818
@@ -0,0 +1,17 @@
✔ free text renders as itself with no citations (1.211164ms)
✔ a cited reply renders its text, then each cite with the badge from the check result (0.457937ms)
✔ a parsed body renders as its string does (0.255138ms)
✔ without a check result a path cite has no badge, no label and is never changed (0.189285ms)
✔ the label and the badge come from the check result only, never from a bad entry (0.162195ms)
✔ a web cite is a link only for https (0.231665ms)
✔ a write notice renders its fixed line (0.167596ms)
✔ a body without the reply or notice shape is free text: the whole body, no citations (0.283804ms)
✔ an empty citations list is a reply with no cites (0.145774ms)
ℹ tests 9
ℹ suites 0
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 73.226696
@@ -0,0 +1,88 @@
TAP version 13
# Subtest: a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
ok 1 - a signed-in same-origin revert reaches exactly one library.revert and returns the new commit
---
duration_ms: 18.873338
type: 'test'
...
# Subtest: a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
ok 2 - a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata
---
duration_ms: 7.061714
type: 'test'
...
# Subtest: a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
ok 3 - a revert takes a project and a 40-hex commit, nothing else, before the bus is asked
---
duration_ms: 6.45503
type: 'test'
...
# Subtest: a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
ok 4 - a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal
---
duration_ms: 6.964122
type: 'test'
...
# Subtest: a revert whose answer is lost or unusable is reported unknown and is never retried
ok 5 - a revert whose answer is lost or unusable is reported unknown and is never retried
---
duration_ms: 5.745801
type: 'test'
...
# Subtest: the status read takes one project query and answers the bus result for that project
ok 6 - the status read takes one project query and answers the bus result for that project
---
duration_ms: 2.815134
type: 'test'
...
# Subtest: a status answer for another project or of the wrong shape is refused; a refusal keeps its status
ok 7 - a status answer for another project or of the wrong shape is refused; a refusal keeps its status
---
duration_ms: 7.105984
type: 'test'
...
# Subtest: the status read needs no send; a read-only Console still has no revert
ok 8 - the status read needs no send; a read-only Console still has no revert
---
duration_ms: 2.002948
type: 'test'
...
# Subtest: library routes are the owner's: a granted user is refused them, and the tester never reverts
ok 9 - library routes are the owner's: a granted user is refused them, and the tester never reverts
---
duration_ms: 0.708324
type: 'test'
...
# Subtest: the page gets the one library renderer, byte for byte, as a JavaScript module
ok 10 - the page gets the one library renderer, byte for byte, as a JavaScript module
---
duration_ms: 1.553573
type: 'test'
...
# Subtest: a revert receipt for another project is unknown and is never retried
ok 11 - a revert receipt for another project is unknown and is never retried
---
duration_ms: 1.371025
type: 'test'
...
# Subtest: a granted-user HTTP session cannot reach owner library callbacks
ok 12 - a granted-user HTTP session cannot reach owner library callbacks
---
duration_ms: 1.586864
type: 'test'
...
# Subtest: a configured revert callback remains unreachable when send is off
ok 13 - a configured revert callback remains unreachable when send is off
---
duration_ms: 1.226995
type: 'test'
...
1..13
# tests 13
# suites 0
# pass 13
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 177.384993
@@ -0,0 +1,20 @@
✔ a signed-in same-origin revert reaches exactly one library.revert and returns the new commit (19.266366ms)
✔ a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata (7.415028ms)
✔ a revert takes a project and a 40-hex commit, nothing else, before the bus is asked (6.176121ms)
✔ a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal (7.325625ms)
✔ a revert whose answer is lost or unusable is reported unknown and is never retried (5.0635ms)
✔ the status read takes one project query and answers the bus result for that project (3.008847ms)
✔ a status answer for another project or of the wrong shape is refused; a refusal keeps its status (6.314096ms)
✔ the status read needs no send; a read-only Console still has no revert (1.440122ms)
✔ library routes are the owner's: a granted user is refused them, and the tester never reverts (0.563979ms)
✔ the page gets the one library renderer, byte for byte, as a JavaScript module (1.180551ms)
✔ a revert receipt for another project is unknown and is never retried (1.12255ms)
✔ a granted-user HTTP session cannot reach owner library callbacks (1.351581ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 182.202771
@@ -0,0 +1,76 @@
✔ a signed-in same-origin revert reaches exactly one library.revert and returns the new commit (18.853567ms)
✔ a revert needs send, the named Host, the user, the exact Origin and same-origin Fetch Metadata (6.491576ms)
✔ a revert takes a project and a 40-hex commit, nothing else, before the bus is asked (5.647113ms)
✔ a bus refusal is reported as not reverted with its code; before C2-02 that is the verb refusal (6.08423ms)
✔ a revert whose answer is lost or unusable is reported unknown and is never retried (5.445934ms)
✖ the status read takes one project query and answers the bus result for that project (3.470803ms)
✖ a status answer for another project or of the wrong shape is refused; a refusal keeps its status (5.42219ms)
✔ the status read needs no send; a read-only Console still has no revert (1.428304ms)
✔ library routes are the owner's: a granted user is refused them, and the tester never reverts (0.509039ms)
✔ the page gets the one library renderer, byte for byte, as a JavaScript module (1.123365ms)
✖ a revert receipt for another project is unknown and is never retried (1.209291ms)
✔ a granted-user HTTP session cannot reach owner library callbacks (1.398731ms)
ℹ tests 12
ℹ suites 0
ℹ pass 9
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 198.061254
✖ failing tests:
test at packages/webui/tests/library-routes.test.mjs:100:1
✖ the status read takes one project query and answers the bus result for that project (3.470803ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
403 !== 405
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/library-routes.test.mjs:107:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 403,
expected: 405,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/library-routes.test.mjs:111:1
✖ a status answer for another project or of the wrong shape is refused; a refusal keeps its status (5.42219ms)
AssertionError [ERR_ASSERTION]: project-not-declared
400 !== 502
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/library-routes.test.mjs:119:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 400,
expected: 502,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/library-routes.test.mjs:145:1
✖ a revert receipt for another project is unknown and is never retried (1.209291ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
200 !== 503
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row225/packages/webui/tests/library-routes.test.mjs:148:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 200,
expected: 503,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,170 @@
diff --git a/packages/bus/tests/channel-proof.test.mjs b/packages/bus/tests/channel-proof.test.mjs
index ba0179832..435d8fa2d 100644
--- a/packages/bus/tests/channel-proof.test.mjs
+++ b/packages/bus/tests/channel-proof.test.mjs
@@ -11,0 +12,2 @@
+// Row 225 (C2-10b) re-pins webui bus.mjs for one STATUS entry, library-revert-not-agent: 409; no proof
+// or transport change.
@@ -32 +34 @@ const baselines = {
- "packages/webui/src/bus.mjs": "604092cb706b152d458f299ff1071f6cd488774629012f96b672f7e9a16c0b2c",
+ "packages/webui/src/bus.mjs": "9f33e9ca4feb7265ae33fd3ddf0ead0b75375d07ecbe9d8e55c618d29edea658",
diff --git a/packages/library/tests/render.test.mjs b/packages/library/tests/render.test.mjs
index b07e26d25..9c861bfe9 100644
--- a/packages/library/tests/render.test.mjs
+++ b/packages/library/tests/render.test.mjs
@@ -3 +3 @@ import assert from 'node:assert/strict';
-import { renderItems, UNREADABLE } from '../src/index.mjs';
+import { renderReply } from '../src/index.mjs';
@@ -4,0 +5 @@ import { renderItems, UNREADABLE } from '../src/index.mjs';
+// Row 225 (C2-10b): renderReply replaces renderItems (design 7.4, the reply schema of 5.2).
@@ -6,6 +7,12 @@ const A = 'a'.repeat(40), B = 'b'.repeat(40);
-const cite = (path, lines, quote, commit = A) => ({ path, commit, lines, quote });
-const options = {
- scopeOf: path => path.includes('/Playground/') ? 'playground' : path.endsWith('.md') ? 'canon' : undefined,
- changed: ({ path, commit }) => path === 'Book One/Characters/Mara Vell.md' && commit === A,
-};
-const render = items => renderItems(JSON.stringify({ items }), options);
+const cite = (path, lines, quote, commit = A, extra = {}) => ({ path, commit, lines, quote, ...extra });
+const web = (url, quote = 'spring tides') => ({ url, quote });
+const reply = (text, citations) => JSON.stringify({ text, citations });
+const CITED = reply("Mara's eyes are grey in chapter 3.", [
+ cite('Book One/Chapters/Chapter 03.md', [41, 41], 'her grey eyes', A, { as: 'canon' }),
+ cite('Book One/Playground/What If.md', [5, 6], 'a storm', B),
+ cite('Notes/Eyes.md', [9, 10], 'green eyes'),
+ web('https://example.invalid/tides'),
+]);
+const CHECK = { citations: [{ scope: 'canon', agent: null }, { scope: 'playground', agent: null },
+ { scope: 'supporting', agent: 'acme/scribe' }, { scope: 'web', agent: null }] };
+const changed = ({ path, commit }) => path === 'Notes/Eyes.md' && commit === A;
@@ -13,21 +20,4 @@ const render = items => renderItems(JSON.stringify({ items }), options);
-// One snapshot per item kind: the plain text both channels show.
-const SNAPSHOTS = {
- fact: [{ kind: 'fact', text: 'Tomas is the ferryman.', cites: [cite('Book One/Characters/Tomas.md', [3, 3], 'the ferryman', B)] }],
- finding: [{ kind: 'finding', text: 'Eye colour differs.', cites: [cite('Book One/Chapters/Chapter 01.md', [41, 41], 'her grey eyes'),
- cite('Book One/Characters/Mara Vell.md', [9, 10], 'green eyes')] }],
- correction: [{ kind: 'correction', text: 'Chapter 1 says grey.', from: 'green eyes', to: 'grey eyes',
- cites: [cite('Book One/Characters/Mara Vell.md', [9, 9], 'green eyes', B)] }],
- question: [{ kind: 'question', text: 'Is the harbor north of the town?', cites: [cite('Book One/Places/Harbor Town.md', [2, 4], 'the harbor', B)] }],
- idea: [{ kind: 'idea', text: 'A storm could close the harbor.', cites: [cite('Book One/Playground/What If.md', [5, 5], 'a storm', B),
- { url: 'https://example.invalid/tides', quote: 'spring tides' }] }],
-};
-const EXPECTED = {
- fact: '1. Fact: Tomas is the ferryman.\n [canon] Book One/Characters/Tomas.md:3 @bbbbbbb "the ferryman"',
- finding: '1. Finding: Eye colour differs.\n [canon] Book One/Chapters/Chapter 01.md:41 @aaaaaaa "her grey eyes"\n'
- + ' [canon] Book One/Characters/Mara Vell.md:9-10 @aaaaaaa "green eyes" (changed since)',
- correction: '1. Correction: Chapter 1 says grey.\n From: "green eyes"\n To: "grey eyes"\n'
- + ' [canon] Book One/Characters/Mara Vell.md:9 @bbbbbbb "green eyes"',
- question: '1. Question: Is the harbor north of the town?\n [canon] Book One/Places/Harbor Town.md:2-4 @bbbbbbb "the harbor"',
- idea: '1. Idea: A storm could close the harbor.\n [playground] Book One/Playground/What If.md:5 @bbbbbbb "a storm"\n'
- + ' [web] https://example.invalid/tides "spring tides"',
-};
+test('free text renders as itself with no citations', () => {
+ for (const body of ['Grey, in chapter 3.', '', '"a JSON string"', '[1, 2]', '42', 'null'])
+ assert.deepEqual(renderReply(body, { check: CHECK }), { kind: 'text', text: body, plain: body });
+});
@@ -35,8 +25,15 @@ const EXPECTED = {
-for (const kind of Object.keys(SNAPSHOTS)) {
- test(`renderItems snapshot: ${kind}`, () => {
- const out = render(SNAPSHOTS[kind]);
- assert.equal(out.kind, 'items');
- assert.equal(out.text, EXPECTED[kind]);
- assert.equal(out.items[0].kind, kind);
- });
-}
+test('a cited reply renders its text, then each cite with the badge from the check result', () => {
+ const out = renderReply(CITED, { check: CHECK, changed });
+ assert.equal(out.kind, 'reply');
+ assert.equal(out.text, "Mara's eyes are grey in chapter 3.");
+ assert.deepEqual(out.citations.map(c => c.badge), ['canon', 'playground', 'supporting', 'web']);
+ assert.equal(out.plain, "Mara's eyes are grey in chapter 3.\n"
+ + ' [canon] Book One/Chapters/Chapter 03.md:41 @aaaaaaa "her grey eyes"\n'
+ + ' [playground] Book One/Playground/What If.md:5-6 @bbbbbbb "a storm"\n'
+ + ' [supporting] Notes/Eyes.md:9-10 @aaaaaaa "green eyes" (agent-written: acme/scribe) (changed since)\n'
+ + ' [web] https://example.invalid/tides "spring tides"');
+ assert.deepEqual(out.citations[0], { badge: 'canon', path: 'Book One/Chapters/Chapter 03.md', commit: A, lines: [41, 41],
+ ref: 'Book One/Chapters/Chapter 03.md:41 @aaaaaaa', quote: 'her grey eyes', canon: true, agent: null, changed: false });
+ assert.deepEqual(out.citations[3], { badge: 'web', url: 'https://example.invalid/tides', link: 'https://example.invalid/tides',
+ ref: 'https://example.invalid/tides', quote: 'spring tides', agent: null, changed: false });
+});
@@ -44,6 +41,2 @@ for (const kind of Object.keys(SNAPSHOTS)) {
-test('renderItems numbers several items and takes a parsed body as well as a string', () => {
- const items = [...SNAPSHOTS.fact, ...SNAPSHOTS.question];
- const out = renderItems({ items }, options);
- assert.equal(out.text, `${EXPECTED.fact}\n${EXPECTED.question.replace(/^1\./, '2.')}`);
- assert.deepEqual(out.items[1].cites[0], { badge: 'canon', path: 'Book One/Places/Harbor Town.md', commit: B, lines: [2, 4],
- ref: 'Book One/Places/Harbor Town.md:2-4 @bbbbbbb', quote: 'the harbor', changed: false });
+test('a parsed body renders as its string does', () => {
+ assert.deepEqual(renderReply(JSON.parse(CITED), { check: CHECK, changed }), renderReply(CITED, { check: CHECK, changed }));
@@ -52,4 +45,4 @@ test('renderItems numbers several items and takes a parsed body as well as a str
-test('without options a path cite has no badge and is never marked changed', () => {
- assert.equal(renderItems({ items: SNAPSHOTS.finding }).text,
- '1. Finding: Eye colour differs.\n Book One/Chapters/Chapter 01.md:41 @aaaaaaa "her grey eyes"\n'
- + ' Book One/Characters/Mara Vell.md:9-10 @aaaaaaa "green eyes"');
+test('without a check result a path cite has no badge, no label and is never changed', () => {
+ const out = renderReply(CITED);
+ assert.deepEqual(out.citations.map(c => [c.badge, c.agent, c.changed]),
+ [[null, null, false], [null, null, false], [null, null, false], ['web', null, false]]);
@@ -58,6 +51,6 @@ test('without options a path cite has no badge and is never marked changed', ()
-const NOTICE_SNAPSHOTS = { 'no-cited-answer': 'No cited answer', 'no-answer': 'No answer', 'turn-failed': 'The turn failed' };
-for (const [notice, text] of Object.entries(NOTICE_SNAPSHOTS)) {
- test(`renderItems snapshot: notice ${notice}`, () => {
- assert.deepEqual(renderItems(JSON.stringify({ items: [], notice }), options), { kind: 'notice', notice, text });
- });
-}
+test('the label and the badge come from the check result only, never from a bad entry', () => {
+ const bad = { citations: [{ scope: 'web', agent: '' }, { scope: 'story', agent: 7 }, null, { scope: 'canon', agent: 'x/y' }] };
+ const out = renderReply(CITED, { check: bad });
+ // A path cite can't be web; a url cite is web by its shape whatever the check says, and never agent-written.
+ assert.deepEqual(out.citations.map(c => [c.badge, c.agent]), [[null, null], [null, null], [null, null], ['web', null]]);
+});
@@ -65,14 +58,8 @@ for (const [notice, text] of Object.entries(NOTICE_SNAPSHOTS)) {
-test('a body without the reply shape shows a fixed line and none of its text', () => {
- const secret = 'free prose the guard would refuse';
- const bodies = [secret, JSON.stringify(secret), '{"items": [], "notice": "other"}', JSON.stringify({ items: [], notice: 'no-answer', extra: secret }),
- JSON.stringify({ items: [] }), JSON.stringify({ items: [{ kind: 'story', text: secret, cites: [] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret }] }), JSON.stringify({ items: SNAPSHOTS.fact, prose: secret }),
- JSON.stringify({ items: [{ kind: 'correction', text: secret, cites: [] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret, cites: [cite('a.md', [2, 1], 'q')] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret, cites: [{ path: 'a.md', commit: 'abc', lines: [1, 1], quote: 'q' }] }] }),
- JSON.stringify({ items: [{ kind: 'fact', text: secret, cites: [{ url: 'https://x.invalid', path: 'a.md', quote: 'q' }] }] }),
- null, 42, '[]'];
- for (const body of bodies) {
- const out = renderItems(body, options);
- assert.deepEqual(out, { kind: 'unreadable', text: UNREADABLE }, String(body));
- assert.ok(!JSON.stringify(out).includes(secret));
+test('a web cite is a link only for https', () => {
+ const urls = { 'https://example.invalid/a': 'https://example.invalid/a', 'http://example.invalid/a': null,
+ 'javascript:alert(1)': null, 'JAVASCRIPT:alert(1)': null, 'data:text/html,x': null, 'not a url': null, '//example.invalid': null };
+ for (const [url, link] of Object.entries(urls)) {
+ const out = renderReply(reply('t', [web(url)]));
+ assert.equal(out.kind, 'reply', url);
+ assert.equal(out.citations[0].link, link, url);
+ assert.equal(out.citations[0].ref, url);
@@ -80,0 +68,27 @@ test('a body without the reply shape shows a fixed line and none of its text', (
+
+test('a write notice renders its fixed line', () => {
+ const body = op => JSON.stringify({ notice: 'library-write', op, path: 'Notes/Eyes.md', commit: B });
+ assert.deepEqual(renderReply(body('write'), { name: 'Scribe' }),
+ { kind: 'notice', op: 'write', path: 'Notes/Eyes.md', commit: B, text: 'Scribe wrote Notes/Eyes.md @bbbbbbb', plain: 'Scribe wrote Notes/Eyes.md @bbbbbbb' });
+ assert.equal(renderReply(body('delete'), { name: 'Scribe' }).text, 'Scribe deleted Notes/Eyes.md @bbbbbbb');
+ assert.equal(renderReply(body('write')).text, 'An agent wrote Notes/Eyes.md @bbbbbbb');
+});
+
+test('a body without the reply or notice shape is free text: the whole body, no citations', () => {
+ const bodies = [
+ JSON.stringify({ text: 't' }), JSON.stringify({ citations: [] }), JSON.stringify({ text: 1, citations: [] }),
+ JSON.stringify({ text: 't', citations: [], extra: 1 }), JSON.stringify({ items: [] }),
+ reply('t', [cite('a.md', [2, 1], 'q')]), reply('t', [cite('a.md', [0, 1], 'q')]), reply('t', [cite('a.md', [1], 'q')]),
+ reply('t', [{ path: 'a.md', commit: 'abc', lines: [1, 1], quote: 'q' }]), reply('t', [cite('a.md', [1, 1], 'q', A, { as: 'playground' })]),
+ reply('t', [cite('a.md', [1, 1], 'q', A, { scope: 'canon' })]), reply('t', [{ url: 'https://x.invalid', path: 'a.md', quote: 'q' }]),
+ reply('t', [{ url: 'https://x.invalid' }]), reply('t', [{ url: 'https://x.invalid', quote: 'q', agent: 'x/y' }]),
+ JSON.stringify({ notice: 'library-write', op: 'move', path: 'a.md', commit: A }),
+ JSON.stringify({ notice: 'library-write', op: 'write', path: 'a.md', commit: 'abc' }),
+ JSON.stringify({ notice: 'library-write', op: 'write', path: 'a.md', commit: A, by: 'x' }),
+ ];
+ for (const body of bodies) assert.deepEqual(renderReply(body, { check: CHECK }), { kind: 'text', text: body, plain: body }, body);
+});
+
+test('an empty citations list is a reply with no cites', () => {
+ assert.deepEqual(renderReply(reply('Grey.', [])), { kind: 'reply', text: 'Grey.', citations: [], plain: 'Grey.' });
+});
@@ -0,0 +1,13 @@
6bdaf870be3d8d03a68eb03f5f6916150bb1b3db134ffbe84018e91d47e6434d packages/bus/tests/channel-proof.test.mjs
0385fc49b445ff730021eb11c20b29027631e0d27350e4591d5a0936ffbdccf6 packages/library/README.md
653ff444ba66cb73e1ca4df1f6037f15a2ec7d3d009cf614a0dc51deabda4b50 packages/library/src/index.mjs
dfcc9f271afc7afdab908395a7f3c402100fa89b4994b20d5bcae3140d5c335e packages/library/src/render.mjs
f3c213f3b0bd080d0bc5837f741a29e324acb91cf0c27144dee08a4113262210 packages/library/tests/render.test.mjs
9f33e9ca4feb7265ae33fd3ddf0ead0b75375d07ecbe9d8e55c618d29edea658 packages/webui/src/bus.mjs
f1a0c9a55b1ae933e205c7e2b9753afe0c2f1fc9b0faa2cf18765396f99ac8dd packages/webui/src/cli.mjs
bf49a22147be0535076640d20149116a744dad9b0b32dd667ee297a061d56134 packages/webui/src/console-bus.mjs
793e7dd9edbe5a55c2deab3c8aef4fbd5b4c838e132e2ac9294043c7176ec6f7 packages/webui/src/public/chat.css
af3a6f382e0d40cf2b28c31db7cb58ba775f1e242651ba42c7426bda99dc0d89 packages/webui/src/public/chat.js
6412fe34bc640433e9cf7610f65d3574a3415c934354d8cb52e056793a81948f packages/webui/src/serve.mjs
f191752f867a112c867ca24268f70aa99ca0903c9208f76bc4bb4c1587cb55df packages/webui/tests/chat-replies-browser.test.mjs
df4ff385ac4ca8244ffa00faf82fcca1f4fcdac8da02a4f4d71233d4df8e1157 packages/webui/tests/library-routes.test.mjs
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Run row225 mutants in disposable snapshots, only on Sage CLEAR.
Commit the tested candidate first. Never change the worktree or shared dependencies.
A timeout, import failure or process failure is not a mutation kill.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import tarfile
import tempfile
BROWSER = 'packages/webui/tests/chat-replies-browser.test.mjs'
ROUTES = 'packages/webui/tests/library-routes.test.mjs'
CHAT = 'packages/webui/src/public/chat.js'
SERVE = 'packages/webui/src/serve.mjs'
RENDER = 'packages/library/src/render.mjs'
MUTANTS = [
('M1-badges', RENDER, 'badge: scope, path:', 'badge: null, path:', BROWSER),
('M1-agent-label', RENDER, 'canon: c.canon, agent,', 'canon: c.canon, agent: null,', BROWSER),
('M2-http-links', RENDER, "new URL(url).protocol === 'https:' ? url : null", 'url', BROWSER),
('M3-wrong-commit', CHAT, 'e.file !== e.last', 'cite.commit !== e.last', BROWSER),
('M3-missing-file', CHAT, "SHA.test(e?.file ?? '') && SHA.test(e?.last ?? '')", "SHA.test(e?.last ?? '')", BROWSER),
('M4-fallback', CHAT, 'Array.isArray(limits.fallback) ? limits.fallback.filter', 'Array.isArray(v.replyLimitsFallback) ? v.replyLimitsFallback.filter', BROWSER),
('M4-problem', CHAT, "if (CODE.test(limits.problem ?? '')) parts.push(`<code>${txt(limits.problem)}</code>`);", '', BROWSER),
('M5-send', SERVE, 'if (!send || !libraryRevert || !named)', 'if (!libraryRevert || !named)', ROUTES),
('M5-project-receipt', SERVE, 'result.project !== value.project || ', '', ROUTES),
('M5-commit-input', SERVE, '!COMMIT.test(value.commit)', 'false', ROUTES),
('M5-origin', SERVE, "if (one(req, 'origin') !== ownOrigin(preview)) return refuse(403, 'cross-origin request refused', 'a revert needs the Console\\'s own Origin');", '', ROUTES),
('M5-fetch-metadata', SERVE, "if (one(req, 'sec-fetch-site') !== 'same-origin') return refuse(403, 'cross-site request refused', 'a revert needs same-origin Fetch Metadata');", '', ROUTES),
('M6-duplicate-call', SERVE, 'result = await libraryRevert({ project: value.project, commit: value.commit });', 'result = await libraryRevert({ project: value.project, commit: value.commit }); await libraryRevert({ project: value.project, commit: value.commit });', ROUTES),
('M7-user-fence', SERVE, "if (path === '/api/chat' || path.startsWith('/api/chat/')) return true;", "if (path.startsWith('/api/library/')) return true; if (path === '/api/chat' || path.startsWith('/api/chat/')) return true;", ROUTES),
]
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--clear', required=True, help='Sage CLEAR authorizing this run')
parser.add_argument('--output', required=True, type=Path)
args = parser.parse_args()
repo = Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip())
if subprocess.check_output(['git', 'status', '--porcelain', '--', 'packages'], text=True).strip():
raise SystemExit('Commit tested packages first: snapshot must match candidate.')
head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
before = {p: digest(repo / p) for p in [CHAT, SERVE, RENDER]}
args.output.mkdir(parents=True, exist_ok=True)
results = []
with tempfile.TemporaryDirectory(prefix='mosaic-row225-mutants-') as directory:
scratch = Path(directory)
archive = scratch / 'candidate.tar'
with archive.open('wb') as stream:
subprocess.run(['git', 'archive', 'HEAD', 'packages', 'roles', 'contracts', 'tasks', 'missions', 'package.json', 'RELEASE'], stdout=stream, check=True)
for name, target, old, new, test in MUTANTS:
tree = scratch / name
tree.mkdir()
with tarfile.open(archive) as bundle:
bundle.extractall(tree, filter='data')
(tree / 'node_modules').symlink_to((repo / 'node_modules').resolve(), target_is_directory=True)
path = tree / target
source = path.read_text()
hits = source.count(old)
if hits != 1:
raise SystemExit(f'{name}: expected one anchor, found {hits}')
path.write_text(source.replace(old, new, 1))
command = ['timeout', '-k', '10', '240' if test == BROWSER else '120', 'node', '--test', '--test-reporter=tap', '--test-concurrency=1', '--test-timeout=30000', test]
run = subprocess.run(command, cwd=tree, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=os.environ)
log = args.output / f'{name}.tap'
log.write_text(run.stdout)
path.write_text(source)
restored = digest(path) == hashlib.sha256(source.encode()).hexdigest()
worktree_clean = before == {p: digest(repo / p) for p in before}
if not restored or not worktree_clean:
raise SystemExit(f'{name}: restore/hash check failed')
killed = run.returncode == 1 and 'ERR_ASSERTION' in run.stdout and 'not ok ' in run.stdout
results.append({'mutant': name, 'anchorHits': hits, 'restored': restored, 'worktreeUnchanged': worktree_clean, 'exit': run.returncode, 'killed': killed, 'test': test, 'log': str(log)})
print(json.dumps(results[-1]), flush=True)
shutil.rmtree(tree)
if not killed:
break
after = {p: digest(repo / p) for p in before}
report = {'head': head, 'clear': args.clear, 'worktreeUnchanged': before == after, 'results': results}
(args.output / 'summary.json').write_text(json.dumps(report, indent=2) + '\n')
if before != after or len(results) != len(MUTANTS) or not all(r['killed'] for r in results):
raise SystemExit(1)
if __name__ == '__main__':
main()
+82
View File
@@ -0,0 +1,82 @@
# Row 305 (#1812, Vikunja 591): shell suites remove the compose network they create
Owner Gizmoduck. Reviewer Filbert. Gate owner Sage. Brief:
docs/plans/2026-10-23_gate-env-rows.md, "Brief: shell suites remove the compose network they create
(Vikunja 591)", at c16e943b9. Base c16e943b9. Mos approved the change in mos-1442 point 2. Mos approved
the two new checks in mos-1445 under mos-1272, adding the presence half.
## The leak, as the code has it at c16e943b9
- `packages/processing-control/src/containers.mjs:16-19`. `containerArgs` builds
`docker compose run --rm ...`. `--rm` removes the container. The project's `<project>_default`
network stays.
- `compose.yaml` has no top-level `name:`, so compose names the project after the checkout
directory. Every gate worktree left one network. By 10-23 they had used up Docker's address pools.
## Does COMPOSE_PROJECT_NAME reach compose? Yes, on every path the two suites take
- Task path. `scripts/run-task.sh:29` execs `node scripts/mosaic-task.mjs`. Nothing scrubs the env
before that: `scripts/common.sh` has no `env -i`, no `unset` and no COMPOSE. `mosaic-task.mjs:326`
sets `spawnEnv = { ...process.env }`, `:405` passes `env: spawnEnv` to `runContainer`, and
`containers.mjs:25` passes those options to `spawnSync('docker', ...)`.
- test-release. `release.sh activate` calls `health_check`, and `health_check` calls `run-task.sh`
at `:54`, which is the task path above. `env MOSAIC_MOCK_RESPONSE=...` adds to the env and keeps
the rest. The suite calls only `status`, `activate` and `rollback`, so `release.sh:122`
`compose build` is off its path.
- test-task seat cases (`:240`, `:315-351`). `agent.sh:209` execs
`packages/processing-control/src/container-cli.mjs`, and `container-cli.mjs:16` calls
`runContainer` with `env: process.env`.
- `build.sh:13`, `release.sh:122` and `verify.sh:40` also run compose. Neither suite calls them.
## Change (scripts/test-release.sh, scripts/test-task.sh only)
- After `SANDBOX="$(mktemp -d)"`, export
`COMPOSE_PROJECT_NAME=mosaic-test-<release|task>-<id>`. `<id>` is the sandbox name after its last
dot, lowercased, alphanumeric only.
- `remove_compose_network` runs `docker network rm "${COMPOSE_PROJECT_NAME}_default"`:
- If there's no docker binary, it returns 0.
- If the error says `not found` or `No such network`, it returns 0.
- On any other failure it prints one stderr line naming the network, then returns 0. That
includes a daemon that is down. Sage kept that line in sage-3367 (a).
- Each existing EXIT trap keeps everything it did and gains `; remove_compose_network` at the end.
The trap's own exit status doesn't change the script's.
- One new check per suite, just before the summary. That is after the last Docker step whether or
not the live cases ran. Both halves sit inside a `docker info` branch:
- the network is present under the run's project name before the explicit
`remove_compose_network` (this proves the export reached compose);
- after the remove, it is absent.
A failing `docker network ls` fails the check. Without the daemon, the check prints a skip line.
- No `docker compose down`, and no other network is touched. No product file changes.
Commits: 541dee2b7 (draft), 3bce852d5 (presence half, mos-1445).
## Gate (Sage's slot, in a gap)
- Before the first run, run `docker network rm` on a name that was never created and record the
daemon's exact wording (sage-3367 b). If the wording matches neither pattern, stop and report to
Sage.
- Each suite runs whole from a gate worktree, under `timeout -k 10 900`. That is far more than
3x the row 302 gate's walls: test-release 3 s, test-task 16 s (mos-1275).
- TMPDIR comes from `mktemp -d /mnt/storage/scratch/tmp/gXXX`.
- Before and after each run, record `docker network ls` filtered to `mosaic-test-`. The run's
network must be absent after.
- Grep `^skip` and name every skip.
- A run killed by the timeout's SIGKILL skips the EXIT trap and leaks its one network. The name is
per-run under `mosaic-test-`, so the gate's after-listing finds it and removes it by its exact name
(Filbert N1; no code change).
- If `docker info` works but there is no network to find, that's a first-run red. It goes to Sage
for Mos, unfixed (mos-1445).
## Mutants (mos-1445)
- (a) Drop the export. The new check must go red, because compose uses the directory's project and
nothing exists under the run's name.
- (b) Make the function's `docker network rm` a no-op. The new check must go red, because the
network is still present after the remove.
- (c) Drop `remove_compose_network` from the trap. This one is a known survivor: the explicit call
covers the normal path, and the trap covers early exits.
Each mutant runs on an isolated copy of the file. Restores are checked by sha256. After each mutant, the network it
leaves is removed by its exact name (mos-1438). Mutant (a) leaves the worktree directory's
`<dir>_default`, and mutant (b) leaves the run's `${COMPOSE_PROJECT_NAME}_default`.
@@ -0,0 +1,88 @@
start 2026-10-23T11:14:01Z HEAD 8ddba89b7 refactor b99ca86b9 docker 29.7.2
== wording
rc=1 stderr: Error response from daemon: network mosaic-test-absent-46829331_default not found
exit status 1
== green
TMPDIR /mnt/storage/scratch/tmp/gIj4
nets[before test-task]:
test-task rc=0 wall 16 s :: selftest: 92 passed, 0 failed
OK compose network made under the run's project and removed after the run
skip: skip live provider cases (row 244: no provider call in the gate)
skip: skip live provider cases (row 244: no provider call in the gate)
nets[after test-task]:
TMPDIR /mnt/storage/scratch/tmp/gDYF
nets[before test-release]:
test-release rc=0 wall 4 s :: selftest: 15 passed, 0 failed
OK compose network made under the run's project and removed after the run
nets[after test-release]:
== mutants
Ma scripts/test-task.sh | 2 +-
TMPDIR /mnt/storage/scratch/tmp/g6SN
nets[before Ma-task]:
Ma-task rc=1 wall 16 s :: selftest: 91 passed, 1 failed
FAIL compose network made under the run's project and removed after the run
red: FAIL compose network made under the run's project and removed after the run
skip: skip live provider cases (row 244: no provider call in the gate)
skip: skip live provider cases (row 244: no provider call in the gate)
nets[after Ma-task]: gizmo-305_default
restore ok scripts/test-task.sh
Ma-task KILLED
cleaned gizmo-305_default
nets[after cleanup Ma-task]:
Ma scripts/test-release.sh | 2 +-
TMPDIR /mnt/storage/scratch/tmp/g48Q
nets[before Ma-release]:
Ma-release rc=1 wall 3 s :: selftest: 14 passed, 1 failed
FAIL compose network made under the run's project and removed after the run
red: FAIL compose network made under the run's project and removed after the run
nets[after Ma-release]: gizmo-305_default
restore ok scripts/test-release.sh
Ma-release KILLED
cleaned gizmo-305_default
nets[after cleanup Ma-release]:
Mb scripts/test-task.sh | 2 +-
TMPDIR /mnt/storage/scratch/tmp/gcm1
nets[before Mb-task]:
Mb-task rc=1 wall 15 s :: selftest: 91 passed, 1 failed
FAIL compose network made under the run's project and removed after the run
red: FAIL compose network made under the run's project and removed after the run
skip: skip live provider cases (row 244: no provider call in the gate)
skip: skip live provider cases (row 244: no provider call in the gate)
nets[after Mb-task]: mosaic-test-task-pf33rz8tic_default
restore ok scripts/test-task.sh
Mb-task KILLED
cleaned mosaic-test-task-pf33rz8tic_default
nets[after cleanup Mb-task]:
Mb scripts/test-release.sh | 2 +-
TMPDIR /mnt/storage/scratch/tmp/gSxg
nets[before Mb-release]:
Mb-release rc=1 wall 3 s :: selftest: 14 passed, 1 failed
FAIL compose network made under the run's project and removed after the run
red: FAIL compose network made under the run's project and removed after the run
nets[after Mb-release]: mosaic-test-release-db4p70csnf_default
restore ok scripts/test-release.sh
Mb-release KILLED
cleaned mosaic-test-release-db4p70csnf_default
nets[after cleanup Mb-release]:
Mc scripts/test-task.sh | 2 +-
TMPDIR /mnt/storage/scratch/tmp/g6WJ
nets[before Mc-task]:
Mc-task rc=0 wall 16 s :: selftest: 92 passed, 0 failed
OK compose network made under the run's project and removed after the run
skip: skip live provider cases (row 244: no provider call in the gate)
skip: skip live provider cases (row 244: no provider call in the gate)
nets[after Mc-task]:
restore ok scripts/test-task.sh
Mc-task survived (known)
nets[after cleanup Mc-task]:
Mc scripts/test-release.sh | 2 +-
TMPDIR /mnt/storage/scratch/tmp/g2jz
nets[before Mc-release]:
Mc-release rc=0 wall 3 s :: selftest: 15 passed, 0 failed
OK compose network made under the run's project and removed after the run
nets[after Mc-release]:
restore ok scripts/test-release.sh
Mc-release survived (known)
nets[after cleanup Mc-release]:
tree clean after mutants
end 2026-10-23T11:15:17Z
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Row 305 gate (sage-3374): wording, both suites whole, mutants a/b/c with exact-name cleanup.
set -uo pipefail
WT=/mnt/storage/scratch/gizmo-305
OUT=/mnt/storage/scratch/gizmo-305-out/gate
mkdir -p "$OUT"
cd "$WT" || exit 9
[ "$(git rev-parse HEAD)" = 8ddba89b7442f09fc71cf24d7f250e40a7f60fdf ] || { echo "HEAD drift"; exit 9; }
[ -z "$(git status --porcelain)" ] || { echo "dirty tree"; exit 9; }
[ -L node_modules ] || ln -s /mnt/storage/src/mosaic-stack/node_modules node_modules
[ -L node_modules ] || { echo "no node_modules link"; exit 8; }
DIRNET=gizmo-305_default
nets() { echo " nets[$1]: $(docker network ls --format '{{.Name}}' | grep -E '^(mosaic-test-|gizmo-305_)' | tr '\n' ' ')"; }
newtmp() { export TMPDIR="$(mktemp -d /mnt/storage/scratch/tmp/gXXX)"; echo " TMPDIR $TMPDIR"; }
run() { # name script
local name=$1 s=$2; newtmp; nets "before $name"
local t0=$SECONDS
timeout -k 10 900 "$s" > "$OUT/$name.log" 2>&1; local rc=$?
echo "$name rc=$rc wall $((SECONDS-t0)) s :: $(grep -E '^selftest:' "$OUT/$name.log")"
[ $rc -eq 124 ] || [ $rc -eq 137 ] && echo "SHELL-PHASE TIMEOUT (mos-1275): to Mos unplaced"
grep -E 'compose network' "$OUT/$name.log" | sed 's/^/ /'
grep -E '^FAIL|could not remove' "$OUT/$name.log" | sed 's/^/ red: /'
grep -E '^skip' "$OUT/$name.log" | sed 's/^/ skip: /'
nets "after $name"
LASTRC=$rc
}
echo "start $(date -u +%FT%TZ) HEAD $(git rev-parse --short HEAD) refactor $(git rev-parse --short origin/refactor) docker $(docker version --format '{{.Server.Version}}')"
echo "== wording"
ABS="mosaic-test-absent-$RANDOM$RANDOM"_default
docker network rm "$ABS" >/dev/null 2>"$OUT/absent.txt"; echo " rc=$? stderr: $(cat "$OUT/absent.txt")"
grep -qE 'not found|No such network' "$OUT/absent.txt" || { echo "STOP: wording matches neither pattern"; exit 3; }
echo "== green"
for s in task release; do run "test-$s" "scripts/test-$s.sh"; [ $LASTRC -eq 0 ] || { echo "STOP: red on green run"; exit 4; }; done
nets "after green" | grep -qE 'mosaic-test-|gizmo-305_' && { echo "STOP: network left after green"; exit 5; }
echo "== mutants"
mut() { # tag python-edit expected(red|green)
local tag=$1 edit=$2 want=$3
for s in task release; do
local f="scripts/test-$s.sh" sum; sum=$(sha256sum "$f" | cut -d' ' -f1)
cp -p "$f" "$OUT/$s.orig"
python3 -B -c "$edit" "$f" || { echo "mutant $tag edit failed"; exit 6; }
git diff --stat -- "$f" | head -1 | sed "s/^/ M$tag /"
run "M$tag-$s" "$f"
cp -p "$OUT/$s.orig" "$f"
[ "$(sha256sum "$f" | cut -d' ' -f1)" = "$sum" ] && echo " restore ok $f" || { echo "RESTORE MISMATCH $f"; exit 7; }
if [ "$want" = red ]; then grep -q '^FAIL.*compose network' "$OUT/M$tag-$s.log" && echo " M$tag-$s KILLED" || echo " M$tag-$s SURVIVED (unexpected)"
else grep -q '^OK.*compose network' "$OUT/M$tag-$s.log" && echo " M$tag-$s survived (known)" || echo " M$tag-$s unexpected red"; fi
# exact-name cleanup (mos-1438)
for n in $(docker network ls --format '{{.Name}}' | grep -E '^(mosaic-test-(task|release)-[a-z0-9]+_default|gizmo-305_default)$'); do
docker network rm "$n" >/dev/null && echo " cleaned $n"
done
nets "after cleanup M$tag-$s"
done
}
E='import sys;p=sys.argv[1];s=open(p).read();o=%s;n=%s;assert s.count(o)==1;open(p,"w").write(s.replace(o,n))'
mut a "$(printf "$E" "'export COMPOSE_PROJECT_NAME='" "'COMPOSE_PROJECT_NAME='")" red
mut b "$(printf "$E" "'err=\"\$(docker network rm \"\$net\" 2>&1 >/dev/null)\" && return 0'" "'err=\"\" && return 0'")" red
mut c "$(printf "$E" "'; remove_compose_network'\"'\"' EXIT'" "''\"'\"' EXIT'")" green
[ -z "$(git status --porcelain | grep -v '^?? node_modules$')" ] && echo "tree clean after mutants" || { echo "TREE DIRTY"; git status --porcelain; }
echo "end $(date -u +%FT%TZ)"
+180
View File
@@ -0,0 +1,180 @@
# Row 307 (#1814, Vikunja 598): move row 270's pi compaction reading behind the harness kind
Owner Gizmoduck. Reviewer Duckworth. Gate owner Sage. Brief:
docs/plans/2026-10-23_harness-kind-compactions.md. Base 1e629c892. Design: DESIGN.md beside this file,
approved (static) by Duckworth on 67daf6403 (duckworth-0280), with C1, C2, N1 and N2 folded in at
cb048b93c. Mos ruled go on the four old-test edits in mos-1459, with the bounds below.
## Commits
1. 1f6db1589, red first. The two new test files and capture.mjs. Its product files are base's:
`git diff --stat 1e629c892 1f6db1589` lists only this packet, capture.mjs and the two tests.
2. 44104adb1, the build. The product files and the four old-test edits.
## What the build does
- `packages/plugins/src/kinds.mjs:52-57`: the harness kind's exports end in `"compactions"`, and the
comment gains its contract line. `interfaceVersion` stays 1 (DESIGN.md "interfaceVersion stays 1").
- New `packages/plugins/harness/pi/compactions.mjs`: base's `scanPi`, `readRange`, `digest`, `STREAM`,
`TAIL`, the chunk size, `PI_VERSION` and `claudeHistory`, unchanged in behaviour, plus the export.
`scanPi` is no longer exported and takes no defaults: its one caller passes all four arguments.
- `pi/index.mjs` re-exports it. `claude-code/index.mjs` answers harness-unsupported with version null.
- `packages/seat/src/compactions.mjs`: keeps the ledger. `readLedger` sets `unsupported` on
`r.reason === "harness-unsupported"` only (:113). `checkAnswer` (:147-164) refuses an answer out of
contract. `collectCompactions(store, { name, compactions }, opts)` (:174-205) calls the export, checks
it, and writes the same lines in the same order as base.
- `packages/cli/src/launcher.mjs`: the `compactions` closure takes the loaded module and calls the
export directly, with the N1 comment on why it skips `call()`. Both call sites pass `harness`.
- `packages/plugins/README.md`: the Harness section gains the `compactions` bullet.
## Old-test edits (mos-1459 bounds)
| File | Edit | Bound |
| --- | --- | --- |
| plugins/tests/kinds.test.mjs | `"compactions"` appended to the pinned list | :17-21, append only |
| seat/tests/compactions.test.mjs | two module imports; `collect` passes `{ name, compactions }` | :11/:21, helper only |
| seat/tests/compactions-budget.test.mjs | pi import; `collect` passes `{ name, compactions }` | :13/:24-25, helper only |
| cli/tests/compactions-launch.test.mjs | pi import; base :344's call (now :345) passes `{ name: "pi", compactions: pi }` | call only, `.count === 1` kept |
No call or expected value in those files changed. harness.test.mjs, cli helpers.mjs:133-151,
launch-routes.test.mjs:17-23 and launcher.test.mjs:694-697 are unedited.
## New tests
- `packages/seat/tests/compactions-ledger.test.mjs`:
- FIXTURES: seven ledgers row 270's code writes (known, store-incomplete, store-unreadable,
not-collected, harness-unsupported from claude-code, harness-unsupported from Claude history, and
torn), each with its streams, projects and steps. Per fixture, test A reads `compactionCount` and
test B runs one collection through `{ name: "pi", compactions: pi.compactions }`.
- Test B runs over the fixture's own streams, not an empty session as DESIGN.md:189 says. Over an
empty session every fixture with a cursor would go store-incomplete (the stream went away), so
"the others go known" could not hold. The expected answers: known stays known (1), store-unreadable
goes known with 0 (the cursor is at the end), not-collected goes known with 1, and
store-incomplete, torn and both harness-unsupported fixtures stay unknown.
- The malformed-answer test (17 answers, plus a module that throws, over a fresh store and over the
known fixture) and the argument test (exact keys; core passes a copy of the cursors).
- `packages/plugins/tests/harness-compactions.test.mjs`: CASES plain, budget (100 bytes), cap (1
stream of 2), shrank and header against pi; pi's `first` with and without Claude history;
claude-code; and a module re-exporting every pi export but `compactions`, refused `missing-export`.
- capture.mjs reads both capture blocks from the test files. It replays each fixture with base's
`collectCompactions(store, "<name>", ...)` and compares the ledger byte for byte, and it runs base's
`scanPi` per case and compares the answer under base's reason order (:301 at base) and
`PI_VERSION`. It prints base's `compactionCount` per fixture: that is the before half.
## Expected red on 1f6db1589
- seat compactions-ledger: the seven test A pass (the ledger rules are base's). Test B fails for known,
store-unreadable and not-collected: base takes the object as a harness that isn't pi and writes
harness-unsupported. Test B passes on base for the other four, whose expected answer is unknown with
the same reason. The malformed-answer and argument tests fail: base never calls the module.
Correction after the run: test B fails on base for claude-history too, so the reds are known,
store-unreadable, not-collected and claude-history. On base the object harness takes :304, which
writes `collected` without a scan, so the count stays null. Pi on the tip scans on a later
collection, finds c1 and answers 1, still unknown with harness-unsupported. The test is right and
this list was wrong (Duckworth agreed, duckworth-0287).
- plugins harness-compactions: the five CASES, the `first` test and the claude-code test fail with a
TypeError (`compactions` isn't exported on base). The missing-export test fails: base's loader loads
the module.
- harness.test.mjs:192 is already red on base. That's the brief's starting point.
## Run plan (mos-1275, `timeout -k 10`)
All on a CLEAR from Sage. TMPDIR comes from `mktemp -d /mnt/storage/scratch/tmp/gXXX`. Gate worktrees
under /mnt/storage/scratch, node_modules linked right after `git worktree add`, pipefail and tee.
| Run | Longest recorded | LIMIT |
| --- | --- | --- |
| capture.mjs on a 1f6db1589 worktree (base and candidate both it) | none | 120 s |
| red first: the two new files on 1f6db1589, each whole | none (seat suite 7 s) | 300 s |
| the touched test files whole on 44104adb1, one at a time | compactions-launch alone: none; other files 15 s | 600 s |
| bus suite | 25 s (270 r5) | 900 s |
| cli suite | 682 s (270 r5) | 2100 s |
| plugins suite | none recorded | 900 s |
| webui suite | 58.2 s (294) | 600 s |
| business suite | none recorded | 900 s |
| seat suite whole, `node --test tests/` (Duckworth C1) | 7 s (270 r5) | 600 s |
| each scripts/test-*.sh | 42 s (test-foundation) | 900 s |
| each mutant, its kill files whole | 1 s (264a probes) | 300 s |
- In the cli run, compactions-launch's T3 tests (base :206, and :305 with the :344 edit; :207, :306
and :345 after the import line) must show as run, not skipped on `sandboxProblem()` (base :36, now
:37). The report names every skip (Duckworth C2).
- Plugins must have zero failures.
- A timeout with no failed test is a hang and goes to Sage. A timeout in the shell phase goes to Mos
unplaced. A first-run red in a new 307 test is placed and reported after (mos-1272). A red in an old
test goes to Sage, unfixed.
## Mutants
Each on an isolated copy of the file, restore checked by sha256, only on a green base.
| # | Change | Kill files |
| --- | --- | --- |
| M1 | `readLedger`: `if (r.harness !== "pi" \|\| r.reason === "harness-unsupported")` restored | plugins harness.test.mjs (:192) |
| M2 | pi `compactions` drops the `first && claudeHistory(store)` branch | seat compactions-budget.test.mjs (base :86, now :87), plugins harness-compactions.test.mjs |
| M3 | `readLedger` never sets `unsupported` | seat compactions-ledger.test.mjs, seat compactions.test.mjs (base :197, now :199) |
| M4 | `collectCompactions` skips `checkAnswer` | seat compactions-ledger.test.mjs |
## Runs
Sage's CLEARs: sage-3455 for steps 1-3, sage-3465 for steps 4-6. Gate worktrees at
/mnt/storage/scratch/g307-base (1f6db1589), g307-tip and g307-mut (1f00d4f91), with node_modules
linked. TMPDIR was /mnt/storage/scratch/tmp/gTIK. Outputs went to /mnt/storage/scratch/gizmo-307-out/s1 and s2.
### Steps 1-3 (13:31:21Z-13:31:36Z, then the capture rerun at 13:32Z)
- capture.mjs crashed with ENOENT on its first run. Base `sessionStore` (session.mjs:78
`privateDirs`, called from :117) makes its directories one level at a time, and :44 passed a
per-fixture root that didn't exist yet. The fix is one line before :44,
`` mkdirSync(join(root, `f-${name}`)) ``. A copy with that line, run on g307-base as both base and
candidate, exited rc 0 and printed `all match`: 7 ledgers byte for byte, 7 base counts and 5 scans.
Base counts: known 1 known; store-incomplete, store-unreadable and not-collected null, unknown
with their own reason; claude-code and claude-history null, harness-unsupported; torn 1,
store-incomplete.
- Red first on 1f6db1589, both rc 1. Seat compactions-ledger: 9 pass, 7 fail (the corrected list
above, plus the malformed-answer and argument tests). Plugins harness-compactions: 0 pass, 8 fail.
Seven fail with `TypeError: pi.compactions is not a function` or `claude.compactions is not a
function`. The missing-export test fails because base loads the module.
- Touched files whole on 1f00d4f91, all rc 0: compactions-ledger 16/16, harness-compactions 8/8,
kinds 5/5, harness 9/9 (:192 green), compactions 20/20, compactions-budget 10/10,
compactions-launch 5/5 with 0 skipped.
### Steps 4-6 (14:00:10Z-14:14:00Z, on 1f00d4f91)
| Run | rc | Time | Result |
| --- | --- | --- | --- |
| bus | 0 | 24 s | 890 pass, 0 fail, 0 skipped |
| cli | 0 | 631 s | 927 pass, 0 fail, 0 skipped; T3 :207, :306, :345 ran |
| plugins | 0 | 0 s | 119 pass, 0 fail, 0 skipped |
| webui | 0 | 57 s | 374 pass, 0 fail, 1 skipped (below) |
| business | 0 | 2 s | 231 pass, 0 fail, 0 skipped |
| seat | 0 | 7 s | 218 pass, 0 fail, 0 skipped |
| test-auth | 0 | 0 s | 15 passed, 0 failed |
| test-conductor | 0 | 7 s | |
| test-config | 0 | 3 s | |
| test-discord | 0 | 7 s | |
| test-extension-package | 0 | 2 s | |
| test-foundation | 0 | 39 s | |
| test-land-work | 0 | 12 s | |
| test-queue | 0 | 20 s | one skip (below) |
| test-release | 0 | 3 s | 15 passed, 0 failed |
| test-task | 0 | 14 s | 92 passed, 0 failed, two skips (below) |
Skips:
- webui: "rebuilding from the source gives the same bytes" needs the pinned esbuild. 307 doesn't
touch the editor.
- test-queue: "queue verify and render --check", because the gate worktree isn't the queue's
canonical root.
- test-task: "live provider cases" twice (row 244, no provider call in the gate).
Mutants ran in g307-mut. Each restore matched its sha256, and the tree was clean after M4.
| # | Killed by |
| --- | --- |
| M1 | plugins harness.test.mjs:192 |
| M2 | seat compactions-budget.test.mjs:87 and :104; plugins harness-compactions.test.mjs:47 |
| M3 | seat compactions-ledger.test.mjs:123 (test B, claude-code and claude-history); seat compactions.test.mjs:199 |
| M4 | seat compactions-ledger.test.mjs:151 (the malformed-answer test) |
There were no reds, so nothing was placed under mos-1272. The sweep found no node or chromium left in
my cwds.
+241
View File
@@ -0,0 +1,241 @@
# Row 307 (#1814, Vikunja 598): row 270's pi compaction reading moves behind the harness kind
Owner Gizmoduck. Reviewer Duckworth. Gate owner Sage. Brief:
`docs/plans/2026-10-23_harness-kind-compactions.md` at 1e629c892, which is the base. Mos ruled the
move in mos-1455 item 3 and declined an EXCEPT entry.
## What is red, as the code has it at 1e629c892
`packages/plugins/tests/harness.test.mjs:192` greps core sources for a quoted harness name. It finds
three lines in `packages/seat/src/compactions.mjs`:
- `:232` in `readLedger`: `if (r.harness !== "pi" || r.reason === "harness-unsupported") unsupported = true;`
- `:283` and `:287` in `collectCompactions`: the `harness === "pi"` branches.
Everything else row 270 put in that file is either harness-free (the ledger, the budget and stream
settings, `storeDir`, `compactionCount`) or pi-only (`scanPi`, `PI_VERSION`, `claudeHistory`, and
the helpers only `scanPi` uses: `readRange`, `digest`, `STREAM`, `TAIL`).
## The new export: `compactions`
`packages/plugins/src/kinds.mjs:54` appends `"compactions"` to the harness kind's exports, so the
loader refuses a module without it (`loader.mjs:61-66`, code `missing-export`).
```
compactions({ store, cursors, budget, streams, first })
-> { found, cursors, known, reason, version }
```
Arguments, all from core:
- `store` is `{ session, projects }`, the two native store directories `sessionStore` makes. The
module never gets `store.dir`, the directory that holds the ledger.
- `cursors` is a copy of the ledger's cursor map, `{ [stream]: { bytes, tail } }`.
- `budget` and `streams` are `readBudget(...)` and `readStreams(...)`, as the launcher passes them
now (`launcher.mjs:299`, `:305`). Core keeps both readers and their defaults.
- `first` is true when the store has no ledger yet (today's `!prior`).
The answer:
- `found`: `[{ entry, completedAt, stream }]`, the compactions read past the cursors. `entry` is a
non-empty string, `completedAt` a string or null, `stream` a non-empty string.
- `cursors`: `{ [stream]: { bytes, tail } }` for each stream the module read. `bytes` is a safe
integer of 0 or more and `tail` 64 lowercase hex.
- `reason`: null, `store-incomplete`, `store-unreadable`, `not-collected` or `harness-unsupported`.
- `known`: a boolean, true exactly when `reason` is null.
- `version`: the store format the module read against as a string, or null when it read nothing.
Core checks the answer before it writes. Anything else throws `harness <name> compactions: bad
answer: <what>` and nothing is appended. A throw from the module itself is the same case. Both reach
the launcher's existing catch (`launcher.mjs:313-316`), which logs the message and records the run's
`compactions` event as unknown with `store-unreadable`, as a scan that throws today does.
### interfaceVersion stays 1
- Every harness module lives in this tree, and the loader loads nothing else (decision 143,
`loader.mjs:1-5`). Both shipped modules gain the export in the same commit.
- A module without the export already refuses at load, as `missing-export` naming `compactions`.
That is the brief's third test. A bump would add a second refusal for the same fact.
- No existing export changes its arguments or answer. Row 125 moved limit-check to 2 because
`mayLaunch`, `mustStop` and `report` changed shape. Nothing like that happens here.
- `harness.test.mjs:36` pins `interfaceVersion` 1, and the brief keeps that file unchanged.
The tradeoff: a module someone wrote against version 1 without `compactions` reports
`missing-export`, not `interface-version`. The message names the export, which is the more useful of
the two.
## What moves where
New file `packages/plugins/harness/pi/compactions.mjs`, imported by `pi/index.mjs`. The loader
checks only `index.mjs` itself (`loader.mjs:42-54`), and claude-code's module already imports files
outside its directory (`claude-code/index.mjs:11-12`). It takes, unchanged in behaviour:
- `scanPi`, with `readRange`, `digest`, `STREAM`, `TAIL` and the chunk size it reads by;
- `PI_VERSION` ("0.85.1");
- `claudeHistory(store)`;
- the reason order `["store-incomplete", "store-unreadable", "not-collected"]` from `:301`;
- the header comment's Pi paragraphs (`compactions.mjs:12-19`, `:21-24`).
pi's `compactions`:
- `first` and `claudeHistory(store)`: answers `{ found: [], cursors: {}, known: false, reason:
"harness-unsupported", version: "0.85.1" }`. That is today's `:283-286`.
- otherwise: `scanPi(store.session, cursors, budget, streams)`, and answers its `found` and
`cursors` with the first reason in the order above, `known` when that's null, and version
"0.85.1". That is today's `:287-302` before the dedupe.
claude-code's `compactions` reads nothing and answers `{ found: [], cursors: {}, known: false,
reason: "harness-unsupported", version: null }`. That is today's `:303-304`. Vikunja 466 still has
no pinned Claude Code format.
## Core keeps the ledger
`packages/seat/src/compactions.mjs` keeps `LEDGER`, `READ_BYTES`, `STREAMS`, `readBudget`,
`readStreams`, `storeDir`, `readLedger`, `compactionCount`, the key and the single O_APPEND write.
`collectCompactions(store, harness, { now, budget, streams })` takes `harness = { name,
compactions }`, the name the route used and the loaded module's export. It:
1. reads the ledger (`prior`), as now;
2. calls `harness.compactions({ store: { session: store.session, projects: store.projects },
cursors: { ...ledger.cursors }, budget, streams, first: prior === null })` and checks the answer;
3. appends, in today's order: one `compaction` line per key the ledger doesn't hold yet, one
`cursor` line per stream whose cursor moved, then `{ v: 1, kind: "collected", at, known, reason,
harness: name, version }`. These are the same lines and field order as today's `:288-302` and
`:304`. `name` is a variable, never a literal;
4. returns `compactionCount(store.dir)`.
`readLedger`'s unsupported test becomes `r.reason === "harness-unsupported"`, a field every answer
carries. No core line names a harness. The core comments drop the quoted names too, because the
grep reads comments.
`packages/cli/src/launcher.mjs:309-318`: the `compactions` closure takes the loaded module too and
calls `collectCompactions(store, { name: model.harness, compactions: harness.compactions }, {
budget, streams })`. Both call sites, `:509` and `:650`, have `harness` in scope (it's
`startLocked`'s `let harness` from `:503`; the `exited.then` callback at `:632` closes over it).
The closure calls the export directly, not through `call()` (`launcher.mjs:196`), because `call()`
turns a throw into a run refusal. The closure gets a comment saying so (Duckworth N1).
`bus/src/runtime.mjs:282` and `broker.mjs:885-889` keep calling `compactionCount` and don't change.
`packages/plugins/README.md`, Harness section, and the comment at `kinds.mjs:35-51` each gain the
`compactions` line with the contract above.
## A ledger row 270 wrote reads the same count
Row 270's code writes a `collected` line in exactly three places:
| Line | harness | reason | version | old rule (`:232`) | new rule |
| --- | --- | --- | --- | --- | --- |
| `:286`, Claude history on the first pi collection | `"pi"` | `harness-unsupported` | "0.85.1" | unsupported | unsupported |
| `:302`, a pi scan | `"pi"` | null, `store-incomplete`, `store-unreadable` or `not-collected` | "0.85.1" | supported | supported |
| `:304`, any other harness name (`claude-code`, or `claude` in the tests) | that name | `harness-unsupported` | null | unsupported | unsupported |
The two rules agree on every line row 270 can write. The rest of `readLedger` (the `bad`, `torn`,
`incomplete` and `events` handling) and all of `compactionCount` don't change. So every ledger row
270 wrote reads the same `{ count, known, reason, at }` after the move. Nothing rewrites a ledger;
the next collection only appends.
The rules would differ only on a `collected` line naming a harness other than `"pi"` with a reason
other than `harness-unsupported`. Row 270 never writes one, and the launcher is the ledger's only
writer (`compactions.mjs:7-10`).
New collections after the move append the same bytes as before for the same store state. pi's
answer is today's scan result, core keeps the dedupe and the line format, and `harness` in the line
is still the route's name.
## Existing tests that change (to Mos through Sage, mos-1272)
Grepped for `interfaceVersion`, the harness exports, `write("harness"`, `fakeHarnesses` and
`collectCompactions(` in `packages/*/tests/*` and `scripts/`.
Four existing files change:
1. `packages/plugins/tests/kinds.test.mjs:17-21`: the pinned harness export list gains
`"compactions"` at the end.
2. `packages/seat/tests/compactions.test.mjs:11` and `:21`: import the two shipped modules. `setup`'s
`collect(harness = "pi")` passes `{ name: harness, compactions: MODULES[harness].compactions }`,
with `pi` mapped to the pi module and `claude` to claude-code's. The 27 `f.collect()` calls and
the two named ones at `:199` and `:201` stay as written.
3. `packages/seat/tests/compactions-budget.test.mjs:13` and `:24-25`: the same change in its
`collect`. Its calls at `:67-69` and `:129-134` stay as written.
4. `packages/cli/tests/compactions-launch.test.mjs:344`: the one direct
`collectCompactions(legacy, "pi")` becomes `collectCompactions(legacy, { name: "pi",
compactions: pi.compactions })`, with an import of the pi module.
Fixtures that build a harness module and need no edit, because they re-export a real module and
inherit `compactions`:
- `packages/cli/tests/helpers.mjs:133-151` `fakeHarnesses`, used by `compactions-launch`,
`idle-stop-race`, `launcher`, `lifecycle`, `session-clear`, `session-launch`, `thread-launch` and
`thread-revoke`;
- `packages/cli/tests/launch-routes.test.mjs:17-23`: `down`, `sparse`, `metered` and `throws`
re-export pi. `broken` lacks every export but `adapter` on purpose. The loader still refuses it at
`levels` first, so `:114` and `:138` (`missing-export`) hold;
- `packages/cli/tests/launcher.test.mjs:694-697`: `down` and `fixture` re-export pi.
`packages/plugins/tests/harness.test.mjs` doesn't change. `:33-37` loops over the kind's exports,
which both modules then have, and `:192` goes green.
No `scripts/test-*.sh` builds a harness module. `test-config.sh:266-280` checks only the two
config keys.
## New tests, red first (mos-1272)
1. `packages/seat/tests/compactions-ledger.test.mjs`: one fixture ledger per reason row 270 writes:
known, `store-incomplete`, `store-unreadable`, `not-collected`, `harness-unsupported` from
claude-code, `harness-unsupported` from Claude history, and a torn ledger.
- Each is literal JSONL in the test. Before the build, in the slot, a capture script runs base
1e629c892's `collectCompactions` with a fixed `now` over the same stores. The literals must
equal its output byte for byte. That proves row 270's code wrote them.
- `compactionCount` reads each one. I run this half on the base and on the candidate, and record
both outputs in BUILD.md as the before and after.
- Then one more collection through `{ name, compactions }` over an empty session gives each
fixture's expected next answer: unsupported and `store-incomplete` stay, the others go known.
This half is red on the base, where core treats the object as a harness that isn't pi.
2. `packages/plugins/tests/harness-compactions.test.mjs`:
- pi's `compactions` over fixture streams answers what base `scanPi` answers, captured by the same
script: a plain count, a budget that stops short (cursor at the last whole line,
`not-collected`), more streams than the cap (no cursor for unopened streams, `not-collected`),
a stream that shrank under its cursor (`store-incomplete`), a non-session header
(`store-unreadable`). Each answer has version "0.85.1".
- pi with `first` and a non-empty `projects` answers `harness-unsupported` without scanning.
Without `first`, the same store scans.
- claude-code answers `harness-unsupported` with version null, over directories that don't exist.
- A harness module that re-exports every other pi export by name, without `compactions`, refuses
at load: `missing-export`, "doesn't export compactions".
3. In the same seat file: core refuses each malformed answer and appends nothing. The cases are
`found` not an array, an empty `entry`, a cursor whose `bytes` is negative or `tail` isn't 64
hex, `known` true with a reason, an unknown reason, a numeric `version`, and a module that
throws. The module gets exactly `{ store: { session, projects }, cursors, budget, streams,
first }`.
## Mutants
- M1, the brief's first: core branches on a harness name again (`readLedger`'s `r.harness !== "pi"
||` restored). Killed by `harness.test.mjs:192`.
- M2, the brief's second: pi's module drops the Claude-history check. Killed by
`compactions-budget.test.mjs:86` and new test 2's `first` case.
- M3, the brief's third: `readLedger` never sets `unsupported`. Killed by new test 1's two
`harness-unsupported` fixtures and `compactions.test.mjs:197`.
- M4, mine: core skips the answer check. Killed by new test 3.
## Gate
Duckworth approves this design, then the manifest. On Sage's CLEAR:
- the capture script on base 1e629c892 first, then the new tests red on the base;
- the bus, cli, plugins (zero failures), webui and business suites, and every `scripts/test-*.sh`;
- the seat suite whole (`packages/seat`, `node --test tests/`), where two old-test edits, new
tests 1 and 3, and the M2 and M3 kills live (Duckworth C1);
- in the cli run, `compactions-launch`'s T3 tests (`:206`, and `:305` with the `:344` edit) must
run, not skip on `sandboxProblem()` (`:36`). BUILD.md names every skip (Duckworth C2);
- the mutants, each on an isolated copy with its restore checked by sha256.
Each suite runs under `timeout -k 10` at 3x or more of its longest recorded wall (mos-1275). I'll
put the numbers in BUILD.md before the first run. TMPDIR comes from
`mktemp -d /mnt/storage/scratch/tmp/gXXX`.
## Round 1
Duckworth approved round 1 on 67daf6403 (duckworth-0280). This revision takes C1 and C2 into
the gate, adds the N1 comment to the launcher plan, and corrects the N2 line refs (`:302`, `:301`).
@@ -0,0 +1,79 @@
// Row 307 (#1814): checks the new tests' literals against row 270's code at base 1e629c892.
//
// node capture.mjs <base checkout at 1e629c892> <candidate checkout>
//
// Reads the FIXTURES and CASES blocks (between `// capture:begin` and `// capture:end`) from the
// candidate's two new test files. Replays each fixture's steps with the base's collectCompactions,
// harness named by string as row 270 takes it, with the clock at 12:00:00 and one second a step,
// and compares the ledger byte for byte. Prints the base's compactionCount for each. Runs the
// base's scanPi over each case and compares the answer the base's reason order gives (:301) with
// version PI_VERSION. Exit 0 only when every one matches. Writes only under TMPDIR.
import assert from "node:assert/strict";
import { appendFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
const [base, cand] = process.argv.slice(2).map((p) => p && resolve(p));
if (!base || !cand) {
console.error("usage: node capture.mjs <base checkout> <candidate checkout>");
process.exit(2);
}
function block(file) {
const text = readFileSync(join(cand, file), "utf8");
const m = /\n\/\/ capture:begin\nconst [A-Z]+ = (\{[\s\S]*?\});\n\/\/ capture:end\n/.exec(text);
if (!m) throw new Error(`${file}: no capture block`);
return JSON.parse(m[1]);
}
const FIXTURES = block("packages/seat/tests/compactions-ledger.test.mjs");
const CASES = block("packages/plugins/tests/harness-compactions.test.mjs");
const seat = await import(pathToFileURL(join(base, "packages/seat/src/compactions.mjs")).href);
const { sessionStore } = await import(pathToFileURL(join(base, "packages/seat/src/session.mjs")).href);
const root = mkdtempSync(join(tmpdir(), "g307-capture-"));
let failed = 0;
const report = (what, same, detail) => {
console.log(`${same ? "MATCH" : "DIFF "} ${what}${detail ? ` ${detail}` : ""}`);
if (!same) failed++;
};
try {
for (const [name, fx] of Object.entries(FIXTURES)) {
mkdirSync(join(root, `f-${name}`));
const store = sessionStore(join(root, `f-${name}`), "acme", "th1", "s1");
for (const [file, text] of Object.entries(fx.streams)) writeFileSync(join(store.session, file), text);
for (const [file, text] of Object.entries(fx.projects ?? {})) writeFileSync(join(store.projects, file), text);
let tick = 0;
const now = () => new Date(Date.UTC(2026, 9, 22, 12, 0, tick++)).toISOString();
for (const step of fx.steps) {
if (step.tear !== undefined) appendFileSync(join(store.dir, seat.LEDGER), step.tear);
else seat.collectCompactions(store, step.collect, { now, ...(step.budget === undefined ? {} : { budget: step.budget }) });
}
const got = readFileSync(join(store.dir, seat.LEDGER), "utf8");
const want = fx.ledger.join("\n") + "\n";
report(`ledger ${name}`, got === want, got === want ? `(${Buffer.byteLength(got)} bytes)` : `\n--- base wrote\n${got}--- test has\n${want}`);
const count = seat.compactionCount(store.dir);
report(`base count ${name}`, JSON.stringify(count) === JSON.stringify(fx.count), JSON.stringify(count));
}
for (const [name, c] of Object.entries(CASES)) {
const dir = join(root, `c-${name}`);
mkdirSync(dir);
for (const [file, text] of Object.entries(c.streams)) writeFileSync(join(dir, file), text);
const scan = seat.scanPi(dir, c.cursors, c.budget, c.cap);
const reason = ["store-incomplete", "store-unreadable", "not-collected"].find((p) => scan.problems.includes(p)) ?? null;
const answer = { found: scan.found, cursors: scan.cursors, known: reason === null, reason, version: seat.PI_VERSION };
let same = true;
try {
assert.deepStrictEqual(answer, c.answer);
} catch {
same = false;
}
report(`scan ${name}`, same, JSON.stringify(scan));
}
} finally {
rmSync(root, { recursive: true, force: true });
}
console.log(failed ? `${failed} differ` : "all match");
process.exit(failed ? 1 : 0);
+342
View File
@@ -0,0 +1,342 @@
# Row 272 / 264e (#1777): session CLI and Console chooser
Owner Huey; reviewer Scrooge per sage-2816 / mos-1310 item 2 (supersedes sage-2796). Current base 5fdfd7275, branch huey/row272-5fdfd7275 (sage-3354). Original draft base 2d0d407ba; round 1 was 850b22680 on b23783f39.
Brief: docs/plans/2026-10-21_t1-sessions.md (ff0be0c79), section 8; row 264 rulings
at 5f5b51dd6. Dependencies: 264a-d landed; 270/271 landing is 7328da8ca.
Count/clear implementation and contracts are now in the current base. No runtime, launcher, library adapter,
role, schema or queue edits belong to this slice.
## Execution and evidence
1. Read session/channel/HTTP/browser contracts and write isolation/validation tests.
2. Add session CLI through the existing verified human channel, preserving defaults.
3. Add authenticated Console chooser, per-session drafts/history/cursors, count and
clear status. Unavailable dependency verbs refuse explicitly; no simulated success.
4. On Sage's CLEAR: claim, run focused tests and required suites/mutants, repair and
record exact evidence. No Node, tests, HTTPS git, claim or push under Gander's gate.
5. After 253 and 269 land, rebase on a new branch name. Review packet to Scrooge,
Mos, Gizmoduck and Sage; push only when permitted.
2026-10-22 preparation: clean worktree switched locally to the requested base. Read
approved brief and all Q1-Q7 rulings. Della notified: no startBroker/library changes.
Gizmoduck asked to pin count/clear contracts. Tests will be written first; Sage's
explicit gate restriction defers RED/GREEN execution. No test result claimed.
Verification plan: CLI real-broker isolation and channel refusal; HTTP strict keys,
Origin/Fetch Metadata, forged selector/refusal and no retries; fixture-browser two
sessions, isolated draft/cursor/receipt, deep-link/refresh, count unknown/known,
read-only writes, clear confirmation/pending. Add selector omission and cursor/draft
sharing mutants. Broader cli/webui/plugins suites use Sage's gate only.
264c/d contracts received gizmoduck-0260: session.list gains compactions and revision/state;
session.clear and session.operation use the pinned envelopes. CLI polls at most 30 s
then reports pending; Console status checks use GET only. Counts absent on 264b show
unavailable; unknown never becomes zero. Clear absent on 264b is disabled with a reason.
USER_VERBS and tester listener write policy remain unchanged (Q7). Existing collaborators
therefore cannot enter the new session surface until the reviewed RBAC integration.
Consumer audit found the explicitly deferred B6 user-reply scope (DEFERRED.md:455-459,
queue-264/BUILD.md:317-322) bound to 272. Added a narrow broker predicate change from
thread to session and a sibling-session regression fixture, without widening user verbs.
This is the only planned broker change and does not overlap Della's runtime adapter.
2026-10-22 static review: added explicit --session to thread read/open and talk/follow;
omission remains the saved default. CLI clear first reads a recorded operation, so a
repeat after a lifecycle revision change cannot become a conflicting second write.
Console deep links name both thread and session; accepted-send receipts, drafts and
history pagination use that tuple. Clear-status GETs refresh the envelope without
reissuing clear. Read failures discard private metadata; late generations cannot paint
another selection. Scrooge is reviewer per sage-2816 / mos-1310, superseding the first
assignment. CLEAR remains after 269, 275, 203 and 207. No execution evidence yet.
## Acceptance / mutant map (prepared, unexecuted)
| Check | Evidence file / intended kill |
| --- | --- |
| T5 legacy human, forged agent, foreign session, strict HTTP identity/Origin/site | session-cli.test.mjs, chat.test.mjs; unchanged USER_VERBS matrix in user-principal.test.mjs |
| T7 saved default, explicit CLI session selector, metadata-only create | session-cli.test.mjs real broker and thread CLI compatibility tests |
| T7 Console chooser, deep link, draft/cursor/receipt isolation, unknown count, confirmation/pending | chat-browser.test.mjs; existing history/sign-out/late-read tests run whole |
| Count/clear contract absence | CLI unavailable refusal; Console disabled clear reason; exact zero vs unknown lower bound tests |
| B6 assigned follow-up | user-principal.test.mjs sibling-session citation refusal and valid same-session reply |
| M1 omit session in chat.mjs messages forwarding | chat.test.mjs explicit selector deepEqual fails |
| M2 collapse browser state key from thread/session to thread | chooser draft isolation assertion fails |
| M3 treat known:false count:0 as exact zero | CLI label assertion and browser unknown-count assertion fail |
| M4 restore B6 mt.thread/s.thread predicate | user-principal sibling refusal assertion fails |
| M5 remove session from CLI thread.send payload | explicit --session talk assertion fails |
| M6 repeat a clear write in place of session.operation poll | CLI exactly-one-clear assertion fails |
Intended focused commands, only after CLEAR, one process at a time:
- `timeout -k 10 300 node --test packages/cli/tests/session-cli.test.mjs`
- `timeout -k 10 300 node --test packages/cli/tests/thread-cli.test.mjs`
- `timeout -k 10 300 node --test packages/bus/tests/user-principal.test.mjs`
- `timeout -k 10 300 node --test packages/plugins/tests/channel.test.mjs`
- `timeout -k 10 300 node --test packages/webui/tests/chat.test.mjs`
- `timeout -k 10 600 node --test packages/webui/tests/chat-browser.test.mjs`
Whole touched suites on Sage's slot: bus (900 s), cli (1800 s, >=3x recorded 591 s),
webui (600 s), plugins (600 s), plus required integration scripts per final gate brief.
Mutants run in disposable copies of the pinned candidate, never against a live stack;
only failed assertions count as kills, never a timeout or import/setup failure.
No test process, browser, provider, credential read or live service action ran in this
preparation. `git diff --check` is the only executed draft verification so far.
Next: Sage CLEAR for claim/focused runs; repair against actual results, then rebase on
a newly named branch once 253 and 269 land. Generate final manifest only after those
changes. Review handoff is Scrooge and Mos, copying Gizmoduck and Sage; no handoff to
Duckworth under the superseded assignment. No candidate commit or green claim yet.
2026-10-22, gizmoduck-0272 / sage-2828 additive contract at 78e14352f: added
session.cancel to CLI/channel/Console with confirmed human writes and strict keys.
Cancelled envelopes retain `retired:null` (old active) or `true` (startup cancelled,
old retired); cancelled is terminal for polling. CLI reports routed message cancellation
with stored ID and exit 3, never waits for delivery or replays it. HTTP maps
session-name-too-long and session-clear-complete without reflecting transport text.
Added unexecuted before/after cancellation, identity/Origin, confirmation and message
receipt tests. Existing clear/create/read wiring is preserved. Asked Gizmoduck whether
the deterministic successor keeps 264a's UUID shape, as selectors currently require.
No Node, test run, claim, commit, push, provider call or live action. Static diff only.
Gizmoduck-0274 resolves the selector compatibility check: successors are lowercase
RFC 9562 v8 UUIDs (variant 10), while 264a sessions are v4. Existing generic UUID
validators accept both and stay unchanged. CLI explicit-selector and Console chooser
fixtures now include mixed v4/v8 IDs to prevent a later v4-only regression. Unexecuted.
## sage-3141 rebase and landed-contract check (2026-10-23)
Reapplied the uncommitted draft onto b23783f39 in the same isolated worktree, on new
branch huey/row272-b23783f39. No candidate commits existed to replay. Preserved the
original draft in the named local stash `huey row272 draft before sage-3141 rebase`.
Product files merged without conflict. Only append-only BUILD-LOG.md and SESSIONS.md
conflicted: kept every new-base byte and appended the original Huey entries unchanged.
No force push, fetch, Node, test, queue write or live change. No claim or candidate
commit yet. Static `git diff --check` clean after resolution.
Checked the actual landed broker, not just the older contract packet:
- broker.mjs:687-707 clearEnvelope supplies the same op/thread/session/successor/state/
start/refusal/at fields and cancelled-only retired:null|true. The generic UUID
selectors still accept both original v4 and successor v8 IDs.
- broker.mjs:6227-6245 session.list supplies compactions, state, revision and successor;
count reader/unknown behavior is the landed 270 contract. Real-base tests must now
exercise these available fields; stand-ins still exercise an unavailable dependency.
- broker.mjs:6270-6330 clear/cancel/operation inputs and receipts match the wired contract.
clear repeats preserve the op; cancel also refuses a failed clear as
session-clear-complete. Our existing refusal mapping already covers it.
- queue-271/BUILD.md:225-238 clarifies cancellation after retirement cancels this clear's
startup, while later deliberate human open/send can start the fresh successor. It
does not permanently disable that new session. No frontend authority change needed.
- store.mjs:17 is 3n; migration is 3m-3n.sql. This slice adds/pins no schema step,
migration blob or schema test count. No DDL copied from an older branch.
Plan after the Gander 574 run-2 hold: on a claims-only CLEAR, claim only; run no Node
or tests beyond the explicit scope of Sage's grant. On test CLEAR run new CLI tests
first, then new broker/HTTP/browser cases and whole touched files, sequentially under
the recorded timeouts. Place/report a first-run red only in a new test; do not alter
an old test, loosen an assertion or fix a second red without Mos's ruling via Sage.
Whole-suite/mutant execution remains on Sage's slot. Candidate commit and push follow
applicable green evidence and an allowed gap, then Scrooge/Mos handoff with final SHA,
manifest, copying Gizmoduck/Sage.
Existing-test edits in the inherited draft need Mos's ruling through Sage before
execution: channel.test.mjs extends its existing supported-verbs table for five
session verbs; chat-browser.test.mjs extends the existing fetch fixture with session
catalog/operation responses and changes its exact send-payload assertion to include
the selected session. Assertions are not loosened. All other added test cases are
new tests. These changes were prepared before sage-3141 and have never run. Ask Sage
to forward this precise delta rather than treating a later old-test red as permission.
sage-3145 correction: refs/stash is shared across worktrees; using it as sole draft
storage was wrong. Pinned the exact draft commit 07dc9d2f7944b4781b5e80dfa18425f4749f7a45
on local branch huey/row272-stash-keep before any stash cleanup. The entry remains
untouched; the branch retains its tracked and untracked snapshots. Future seat draft
preservation uses owned branches/local commits, not shared stash positions.
Sage explicitly directed a local draft commit before tests to preserve the rebased
work. This overrides the normal green-before-commit gate for this storage checkpoint
only. It is an untested draft, not an approved candidate, test result, push or landing.
Claims await the post-574-run-2 CLEAR; test execution awaits Mos's ruling and Sage's
separate test CLEAR. Old-test deltas were forwarded as sage-3144; no ruling assumed.
## Scrooge early static read, 850b22680 (2026-10-23)
Review key scrooge-272-early-read-huey-1. B1 confirmed: the render catch used any
403/404 to delete every sibling's draft/receipt and clear/create operation key.
Uncommitted follow-up restricts that purge to thread-not-owned, thread-not-found and
access-refused. Session-not-found still shows an unavailable read, preserving sibling
state and pending recovery coordinates. New browser test retains A's draft and a lost
clear key across a bad-session deep link, then proves Clear queries the retained op
without another write.
N1: malformed clear/cancel write receipts now add the same redacted outcome-unknown
audit line as other writes. New HTTP test checks one write log and no read log.
N2: a valid recorded clear belonging to another session now refuses with a precise
operation-key-reuse message (exit 2), rather than ambiguous outcome unknown; new CLI
test proves no clear write. Malformed envelopes retain the outcome-unknown refusal.
No approved old assertion or shared stub edited. Static diff check only; all new tests
remain unrun pending Sage's test CLEAR. Published round 1 still pins 850b22680 and its
original manifest; these follow-up edits do not replace that evidence. No new commit,
push, queue write, provider call or live change for this early-read follow-up.
Execution watch: inspect message-filter.test first if the bus suite reports a B6
fixture red (manual message_threads rows must name the run's bound session); do not
change an old fixture/assertion without Mos's ruling via Sage. thread-shares uses the
persisted session_defaults mapping per Scrooge's check. No runtime verdict claimed.
## Granted-user legacy compatibility (sage-3357; pre-candidate design)
219/295 are retained on base 5fdfd7275. USER_VERBS still excludes session verbs;
193/T5 user session grants are a separate DEFERRED item, outside 272. No owner
transport carries a user session operation. Bounded negotiation applies only to the
session.list read: only `access-refused` and an omitted-session route permit legacy
thread-default read/send/open. All other errors keep the unavailable/error state, and
explicit session links always refuse. On fallback session controls are hidden and one
status line says sessions are not available for this account. No fake session ID,
count or lifecycle status is invented; the broker continues choosing its persisted
default under current grants on every existing thread request. Owner routes keep the
normal chooser and explicit selected-session arguments.
New file chat-sessions-access.test.mjs uses the real broker with an already granted
principal over loopback and the existing repository browser fixture. It checks legacy
read/send/open with no session controls, explicit-link refusal, another-error refusal,
and unchanged owner chooser. No shared stub or existing assertion changes. The three
required mutants remove the access-refused check, remove the omitted-session check,
and display the controls during fallback; guard tests must kill each by assertion.
RED-first slot sage-3358: only this new file, before fallback implementation. Expected
run 100 s including four Chromium fixtures; bound 300 s (3x), command
`timeout -k 10 300 node --test packages/webui/tests/chat-sessions-access.test.mjs`.
TMPDIR must be assigned from mktemp -d, with no underscore. No compose service is used;
check/remove the generated directory's compose default network if any exists. Expected
legacy-positive red is placed/reported afterward under mos-1272. Any other red is
reported to Sage before a fix. No other test file/suite is authorized in this slot.
sage-3358 RED run completed before the fallback implementation. Only the new
chat-sessions-access.test.mjs ran, under the 300 s bound, TMPDIR allocated by
`mktemp -d /tmp/huey272-red-XXXXXXXX` as /tmp/huey272-red-0q66Jcf4. Exit 1;
4 tests, expected legacy-positive assertion red and 3 passes, no other reds/skips/
cancellations/timeouts, 3.504 s. Exact output: r2-red-access.tap. No matching compose
network existed. Slot returned to Sage/Mos as huey-0035; no further tests run.
mos-1442 / sage-3361 adds explicit timeout, bus-error and malformed-answer refusal
coverage. The new private fixture adds those guard cases (unrun), with a widening-key
mutant alongside the access-refused/omitted-selector/control mutants. No shared stub
or existing assertion changed. Native preview status/open both reported unavailable
in this headless environment before the authorized repository-browser regression.
Implementation now local: the catalog request catches only access-refused on omitted
session routes. Its internal null result selects the existing broker-chosen thread
default with no fabricated session ID. Read/send/open omit the session property in
that mode. One status line replaces all session controls. Explicit links and any
other failure never select that mode. USER_VERBS and all owner transport routing
remain byte-unchanged. Guard cases are preserved on the pre-fallback baseline; their
RED evidence comes from the required mutants, as Sage accepted in sage-3358.
Fallback mutants are executable via fallback-mutants-r2.py, only on Sage's slot.
It copies packages/roles into a private mktemp directory and mutates only the copied
chat.js, never candidate bytes. Each run has a 300 s bound and requires a named
assertion failure; timeout/import/setup failures do not count. F1 removes the exact
access-refused key (human-required guard); F2 removes the omitted-selector check;
F3 shows session controls; F4 widens the key to timeout/unreachable, no-bus-host and
invalid-response (timeout guard). The test file also independently checks bus-error
and malformed-answer refusal. These mutants and the post-implementation tests have
not run locally. The earlier six non-fallback mutants in this packet remain required
in the full gate; the new runner covers the four fallback mutants only.
## Round 2 rebase receipt (sage-3354)
Queue rev 3510 returned row 272 to in-progress. On new branch huey/row272-5fdfd7275,
replayed round 1 and the preserved B1/N1/N2 fixes onto 5fdfd7275 (219, after 295).
Resolved exactly the seven named conflicts. Test append conflicts retain both complete
sets of tests; existing test content equals the new base plus only the mos-1388 hunks.
No old assertion or shared stub was changed beyond those approved hunks. Static byte
comparison confirms the entire upstream test file prefixes and approved modifications.
Both append-only logs retain the complete 5fdfd7275 prefix plus Huey's entries.
219's per-instance thread picker, explicit createThread instance forwarding, choice
refresh/revocation behavior and tests remain present. 295's createChat owner-settings
read stays unchanged: only plugins uses the owner transport; user chat verbs stay on
the user transport. Removed a duplicate access-refused status entry introduced by
combining those patches. USER_VERBS is unchanged. No schema or migration changed.
This is a pre-gate candidate per Sage's explicit instruction to commit/push/request
round 2 before Sage runs the full gate. Only the expected-red new fallback test ran
under sage-3358. No green test claim, no suite/mutant result and no old-file run is
implied. Every touched test file still needs its whole-file run on Sage's slot, plus
all touched suites and mutants. Any first-run red in launcher/creative/chat-browser
returns to Mos via Sage before fixing. TMPDIR for gate runs comes only from mktemp -d,
with no underscore; remove a matching compose default network only if created.
## Round 3 preparation — gate run 1 (sage-3433)
Sage ran candidate b44332a93 over refactor 1e4c1e6e9 with the pinned esbuild
and MOSAIC_REQUIRE_EDITOR_BUILD=1. Gate RED: bus 891/0, business 231/0,
CLI 940/1, plugins 110/1, webui 372/20. No mutants ran. Sage reproduced
every failing file on b44332a93 alone, excluding overlay drift as the cause.
Evidence: /mnt/storage/scratch/sage-r272/run-*.txt and split-*.txt.
A: the new session CLI real-broker fixture omitted role authority. Verified
checkBusiness requires withinRole and crossRole arrays on every role; added
empty arrays to chief and assistant in that fixture only, as sage-3433 permits.
No production authority or existing assertion changed. No local tests ran.
B remains held for Mos under mos-1316/1317: initial bare-chat rendering compares
old?.id with undefined id, then dereferences null old at the sessions/session/key
initializers. Sage proposes old && old.id === id at all three sites. No B edit
made pending the ruling. C (plugins harness compactions) is separately owned.
Round 3 is not committed or pushed yet. The manifest path set remains the same
17 paths; regenerate hashes when the authorized fixes are complete. Future
launcher, creative and chat-browser runs use a TMPDIR allocated with
mktemp -d /mnt/storage/scratch/tmp/<alnum>XXXXXX, because bwrap hides /tmp.
Sage owns the next full gate. No tests, repo Node or queue writes here.
### Round 3 candidate — mos-1455 / sage-3435
Mos authorized B: all three initial render comparisons now use old && old.id === id,
so an initial list route cannot dereference null old. A remains fixture-only.
chat-browser.test.mjs and granted-user.test.mjs are byte-identical to b44332a93.
No pre-existing assertion, authority validator or shared stub changed.
Frozen manifest: manifest.r3.txt, 17 paths unchanged; SHA256 f98c5b7a884ed1e5951a584227ece16d403baf62199d405aaa1981d667e9e075.
Parent candidate: b44332a9360504291dda3f81131c934b286f3226; base remains 5fdfd7275.
Only session-cli.test.mjs and public/chat.js product hashes differ from round 2.
This follow-up commit/push is authorized before the round 3 gate by sage-3435.
No local tests, repo Node, mutants or queue writes ran. Gate remains RED until
Sage reruns; plugins harness compactions is a separately owned dependency, and
Mos declined an EXCEPT edit. Scrooge reviews this A/B delta; Sage posts round 3.
## Round 4 — scoped browser heading waits (mos-1460/1461)
Round 3 gate on 72690cae7: bus 891/0, CLI 941/0, business 231/0; plugins
had only the allowed exact base red, checked by Mos; webui 390/2. Both reds
were newly added chat-browser missing-session waits at lines 361 and 437.
Whole-file rerun reproduced 17/2. No round 3 green acceptance claim.
Diagnosis authorized by sage-3450/3453 and mos-1460: unchanged whole-file run
17/2, 28.223 s; scratch-only CDP probes showed the route reaching chat.js:176,
catalog rows A/B without M, and catch receiving session-not-found with status
404. DOM had first h1 "Control board" (shell), then .chat-area h1 "Thread
unavailable". Product refusal and painting worked. Evidence retained under
/mnt/storage/scratch/tmp/hueyP1enyB/ (baseline.tap, probe.tap, probe-catch.tap,
proposed.diff); scratch probe source removed, tracked tree remained clean.
mos-1461 / sage-3458 authorizes exactly two selector changes: document-wide
h1 becomes .chat-area h1 in those waits. Expected text, timeout and every
other line of chat-browser.test.mjs remain byte-identical to 72690cae7.
This scopes the same assertion to its component; it does not weaken the
composer/write/draft/recovery checks. No production code changes in round 4.
manifest.r4.txt retains the same 17 paths; SHA256 a47ed1a23fb98c1ffe6cbcc4e7130e5c515387a2c5dc41e5ae1cbf04640d8c1b.
Only chat-browser.test.mjs hash differs from r3. New local follow-up commit
is authorized before execution; push and single-file run await Sage CLEAR
while Gizmoduck owns the slot. Required pass: whole chat-browser.test.mjs,
19/19 and skipped 0, timeout -k 10 300, TMPDIR from
mktemp -d /mnt/storage/scratch/tmp/hueyXXXXXX. No round 4 run yet.
After green, claimant moves row to in-review; Sage posts request and
Scrooge reviews the exact two-line delta. Full five-suite gate remains Sage's.
+1
View File
@@ -0,0 +1 @@
5fdfd7275ce3048e488d93dcdb1f9f2aa5961a69
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Run only on Sage's slot. Mutate disposable sources, never the candidate tree."""
import argparse
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
GUARD = "if (err.code === 'access-refused' && !match[3]) return null;"
CONTROLS = " if (current.legacyDefault) return '<p role=\"status\">Sessions are not available for this account.</p>';"
MUTANTS = {
'F1-any-code': (GUARD, "if (!match[3]) return null;", 'human-required catalog failure'),
'F2-explicit-link': (GUARD, "if (err.code === 'access-refused') return null;", 'granted explicit-session route'),
'F3-controls': (CONTROLS, ' // mutant: show session controls on a legacy fallback', 'granted legacy route'),
'F4-widened-key': (GUARD, "if (['access-refused', 'unreachable', 'no-bus-host', 'invalid-response'].includes(err.code) && !match[3]) return null;", 'timeout catalog failure'),
}
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('--out', type=Path, required=True)
ap.add_argument('--mutant', choices=list(MUTANTS), action='append')
args = ap.parse_args()
source = Path(__file__).resolve().parents[4]
args.out.mkdir(parents=True, exist_ok=True)
for name in args.mutant or MUTANTS:
# Explicit mktemp, no underscores: same TMPDIR rule as the real gate.
scratch = Path(subprocess.check_output(['mktemp', '-d', '/tmp/huey272-fallback-mutant-XXXXXXXX'], text=True).strip())
try:
for directory in ['packages', 'roles']:
shutil.copytree(source / directory, scratch / directory)
if (source / 'node_modules').exists():
(scratch / 'node_modules').symlink_to((source / 'node_modules').resolve(), target_is_directory=True)
for filename in ['package.json', 'RELEASE']:
if (source / filename).exists():
shutil.copy2(source / filename, scratch / filename)
path = scratch / 'packages/webui/src/public/chat.js'
old, new, target = MUTANTS[name]
text = path.read_text()
if text.count(old) != 1:
raise RuntimeError(f'{name}: mutation anchor is not unique')
path.write_text(text.replace(old, new, 1))
env = dict(os.environ, TMPDIR=str(scratch))
result = subprocess.run(['timeout', '-k', '10', '300', 'node', '--test', '--test-reporter=tap',
'packages/webui/tests/chat-sessions-access.test.mjs'],
cwd=scratch, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
(args.out / f'{name}.tap').write_text(result.stdout)
killed = result.returncode == 1 and 'ERR_ASSERTION' in result.stdout and bool(
re.search(r'not ok [^\n]*' + re.escape(target), result.stdout))
print(json.dumps({'mutant': name, 'returncode': result.returncode, 'assertion_kill': killed}), flush=True)
if not killed:
raise RuntimeError(f'{name}: not an assertion kill; stop, diagnose and report before any fix')
finally:
# These tests never use compose. Remove the disposable copy after browser teardown;
# retained TAP lives only in --out. No live network/volume/service cleanup is performed.
shutil.rmtree(scratch)
if __name__ == '__main__':
main()
@@ -0,0 +1,16 @@
fb518c370aeb531220de123464f0a809156fc546b51d7195f4f9ca660409cf22 packages/bus/src/broker.mjs
35d044b0904ce913b3356b02cdc5bf22babecf738b511dca80c75f704e4ef78e packages/bus/tests/user-principal.test.mjs
e795bc4ee5aa038835edfeabe3498e53fdd700df478198cac6767498e2fac0b7 packages/cli/README.md
96c4f93628964b6ea10fb87d5cf936f9ecf2e7286aa20c0914ae8b0fb32c47e7 packages/cli/src/cli.mjs
0c31ac7260e77556f30f33a9932dc59a30f394982d8556650af7c521d2bcc947 packages/cli/src/session.mjs
58888b7731f8009d4c3f8be2caf4445f13dd8f534e1633f6e2720b259fcc3f92 packages/cli/src/thread.mjs
27fb7dd91007ec5f729fbe854219f423177cf4db2d44d1f8b04c7e8c5e6db90a packages/cli/tests/session-cli.test.mjs
5e7e1ec169392ee3065f8f47d19732ebbe8bdd90f9193581425b956e66da6b73 packages/plugins/channel/cli/index.mjs
aa086c365c4a6b0eaf84359a20ef2b30e723013ada6573fb0133163d5bf98a74 packages/plugins/channel/webui/index.mjs
143bf601badf5cc2b25cb24368ace2ec34840abec858d272af239d6a4ddcdecb packages/plugins/tests/channel.test.mjs
0720f66a57d965c9275f598b7c7ac438f238a0fac52808fc8f10312b85ba2727 packages/webui/src/chat-http.mjs
65fd3fd2337aaa88245a9fb10b55fd872f9150085ff9a53fe522e758a93c64fa packages/webui/src/chat.mjs
88dd353f5df0603ed1fd43dd543ef7b45af93fee72b6bb6a03a26c068202fbd3 packages/webui/src/public/chat.css
074d570aee4e2b6cdcbd6b8be399a2a21e863f2d620f95dad8d93ffc7d9c46da packages/webui/src/public/chat.js
2161c4830a3f73f0343e76521b948b933e401f7b9ba4ef8a5c75bf99c565d6b6 packages/webui/tests/chat-browser.test.mjs
b79a8809951580187b581450cdd4012245f2d88a371bf7d2f123580af8815449 packages/webui/tests/chat.test.mjs
@@ -0,0 +1,17 @@
c21f1bd9af6841126baf19f5c9926ca23ba8e56b9af342373d8e4fefcee63632 packages/bus/src/broker.mjs
3957243d59e50eaa2326b88b9205ed8e8b5d33bde2ce1ef62e68f20e56283568 packages/bus/tests/user-principal.test.mjs
0a920131a981c2a1c9b73428a998966f446babae4dfe7d10eebdd6455571c0c9 packages/cli/README.md
96c4f93628964b6ea10fb87d5cf936f9ecf2e7286aa20c0914ae8b0fb32c47e7 packages/cli/src/cli.mjs
1a30048032f5bb64419ae9716a1692978bc1a137220a094d1900298aca6ba1d7 packages/cli/src/session.mjs
58888b7731f8009d4c3f8be2caf4445f13dd8f534e1633f6e2720b259fcc3f92 packages/cli/src/thread.mjs
359c9fbfe30d860046ee3d29d23681900d9c73b3a4c3a91cd2f4e1d2c6be143e packages/cli/tests/session-cli.test.mjs
5e7e1ec169392ee3065f8f47d19732ebbe8bdd90f9193581425b956e66da6b73 packages/plugins/channel/cli/index.mjs
aa086c365c4a6b0eaf84359a20ef2b30e723013ada6573fb0133163d5bf98a74 packages/plugins/channel/webui/index.mjs
143bf601badf5cc2b25cb24368ace2ec34840abec858d272af239d6a4ddcdecb packages/plugins/tests/channel.test.mjs
6e743878e507be20d87ae10afdb01c105f6f4fab76d02410e065f3f20d18e597 packages/webui/src/chat-http.mjs
7a5a18a9879fe7132f20a651c6927b007475571bd8fc45314eda9d6809288cea packages/webui/src/chat.mjs
88dd353f5df0603ed1fd43dd543ef7b45af93fee72b6bb6a03a26c068202fbd3 packages/webui/src/public/chat.css
e4c25743a6d8cbe15eaac2b4d8c3bd309ffa5793f35584d862690a9d89725b72 packages/webui/src/public/chat.js
1aa313f1602cf87a0ddac145bf58bbd7f1073c02009c65797e85418fdfdbd838 packages/webui/tests/chat-browser.test.mjs
23de8678566f24a6c8bf0ca75b4550969015b06253586070e229f4eaf0bba2b4 packages/webui/tests/chat-sessions-access.test.mjs
773538c0c6a26ab525aa30e588249ba4b4ee981129533db0348f2107da5dd1df packages/webui/tests/chat.test.mjs
@@ -0,0 +1,17 @@
c21f1bd9af6841126baf19f5c9926ca23ba8e56b9af342373d8e4fefcee63632 packages/bus/src/broker.mjs
3957243d59e50eaa2326b88b9205ed8e8b5d33bde2ce1ef62e68f20e56283568 packages/bus/tests/user-principal.test.mjs
0a920131a981c2a1c9b73428a998966f446babae4dfe7d10eebdd6455571c0c9 packages/cli/README.md
96c4f93628964b6ea10fb87d5cf936f9ecf2e7286aa20c0914ae8b0fb32c47e7 packages/cli/src/cli.mjs
1a30048032f5bb64419ae9716a1692978bc1a137220a094d1900298aca6ba1d7 packages/cli/src/session.mjs
58888b7731f8009d4c3f8be2caf4445f13dd8f534e1633f6e2720b259fcc3f92 packages/cli/src/thread.mjs
a32bfeecd8589d41b298317954fae172dc936ee0559ec37990e84ed03bd38b07 packages/cli/tests/session-cli.test.mjs
5e7e1ec169392ee3065f8f47d19732ebbe8bdd90f9193581425b956e66da6b73 packages/plugins/channel/cli/index.mjs
aa086c365c4a6b0eaf84359a20ef2b30e723013ada6573fb0133163d5bf98a74 packages/plugins/channel/webui/index.mjs
143bf601badf5cc2b25cb24368ace2ec34840abec858d272af239d6a4ddcdecb packages/plugins/tests/channel.test.mjs
6e743878e507be20d87ae10afdb01c105f6f4fab76d02410e065f3f20d18e597 packages/webui/src/chat-http.mjs
7a5a18a9879fe7132f20a651c6927b007475571bd8fc45314eda9d6809288cea packages/webui/src/chat.mjs
88dd353f5df0603ed1fd43dd543ef7b45af93fee72b6bb6a03a26c068202fbd3 packages/webui/src/public/chat.css
68696f41a7335db23c0786537de76ff1202142518647d3d7b89b6b6503baf4f1 packages/webui/src/public/chat.js
1aa313f1602cf87a0ddac145bf58bbd7f1073c02009c65797e85418fdfdbd838 packages/webui/tests/chat-browser.test.mjs
23de8678566f24a6c8bf0ca75b4550969015b06253586070e229f4eaf0bba2b4 packages/webui/tests/chat-sessions-access.test.mjs
773538c0c6a26ab525aa30e588249ba4b4ee981129533db0348f2107da5dd1df packages/webui/tests/chat.test.mjs
@@ -0,0 +1,17 @@
c21f1bd9af6841126baf19f5c9926ca23ba8e56b9af342373d8e4fefcee63632 packages/bus/src/broker.mjs
3957243d59e50eaa2326b88b9205ed8e8b5d33bde2ce1ef62e68f20e56283568 packages/bus/tests/user-principal.test.mjs
0a920131a981c2a1c9b73428a998966f446babae4dfe7d10eebdd6455571c0c9 packages/cli/README.md
96c4f93628964b6ea10fb87d5cf936f9ecf2e7286aa20c0914ae8b0fb32c47e7 packages/cli/src/cli.mjs
1a30048032f5bb64419ae9716a1692978bc1a137220a094d1900298aca6ba1d7 packages/cli/src/session.mjs
58888b7731f8009d4c3f8be2caf4445f13dd8f534e1633f6e2720b259fcc3f92 packages/cli/src/thread.mjs
a32bfeecd8589d41b298317954fae172dc936ee0559ec37990e84ed03bd38b07 packages/cli/tests/session-cli.test.mjs
5e7e1ec169392ee3065f8f47d19732ebbe8bdd90f9193581425b956e66da6b73 packages/plugins/channel/cli/index.mjs
aa086c365c4a6b0eaf84359a20ef2b30e723013ada6573fb0133163d5bf98a74 packages/plugins/channel/webui/index.mjs
143bf601badf5cc2b25cb24368ace2ec34840abec858d272af239d6a4ddcdecb packages/plugins/tests/channel.test.mjs
6e743878e507be20d87ae10afdb01c105f6f4fab76d02410e065f3f20d18e597 packages/webui/src/chat-http.mjs
7a5a18a9879fe7132f20a651c6927b007475571bd8fc45314eda9d6809288cea packages/webui/src/chat.mjs
88dd353f5df0603ed1fd43dd543ef7b45af93fee72b6bb6a03a26c068202fbd3 packages/webui/src/public/chat.css
68696f41a7335db23c0786537de76ff1202142518647d3d7b89b6b6503baf4f1 packages/webui/src/public/chat.js
12250a4f052489602182a49779fa67b49c7eed9d2f4c3ed7077306b2c360a344 packages/webui/tests/chat-browser.test.mjs
23de8678566f24a6c8bf0ca75b4550969015b06253586070e229f4eaf0bba2b4 packages/webui/tests/chat-sessions-access.test.mjs
773538c0c6a26ab525aa30e588249ba4b4ee981129533db0348f2107da5dd1df packages/webui/tests/chat.test.mjs
@@ -0,0 +1,32 @@
✖ 272 fallback: granted legacy route keeps default history, send and open without session controls (967.088999ms)
✔ 272 fallback guard: granted explicit-session route refuses without default history or writes (780.341865ms)
✔ 272 fallback guard: another catalog error never enables default-thread access (765.314522ms)
✔ 272 fallback guard: owner route retains chooser and explicit default-session addressing (901.74851ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3503.583341
✖ failing tests:
test at packages/webui/tests/chat-sessions-access.test.mjs:74:1
✖ 272 fallback: granted legacy route keeps default history, send and open without session controls (967.088999ms)
AssertionError [ERR_ASSERTION]: legacy granted-user thread must remain usable after session.list access-refused
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/huey-system-manager-design/packages/webui/tests/chat-sessions-access.test.mjs:77:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
+66
View File
@@ -0,0 +1,66 @@
# Row 300 build notes (Launchpad): R6-N1, an HTTP-level test of the user-session option swap (#1807)
Brief: docs/plans/2026-10-23_r6-followup-rows.md § Brief: R6-N1. Reviewer Duckworth, gate owner Sage.
Base c13c337e1 (refactor, after row 294 landed at d3059c2c3). Branch launchpad/row300.
## Change
Tests only. One new file, packages/webui/tests/user-option-swap.test.mjs. No product change and no
existing assertion or fixture touched.
- **Gate stubbed open.** The file registers an in-thread load hook (`registerHooks`, as
thread-claims.test and library-cli.test do through `--import`) keyed on the one URL
`serve.mjs?gate=open`. On that URL alone it prepends `return true;` to `userRoute`'s body, then
imports `startServer` from it. Every other import of serve.mjs, and every relative import inside
the copy, is the real module. If the gate's signature line is not in serve.mjs exactly once, the
hook throws. A renamed gate then fails loudly, instead of quietly testing the closed gate. The
test also asserts that the copy's `userRoute` answers true for `POST /api/bus/message`.
- **Setup.** This is granted-user.test.mjs's setup, cut to what the test needs: the fake OP, the
in-process broker for `demo`, createSso with the access hook, and `userOptionsFor` over alice's
transport. Every owner option is a stub that records its own name. The owner transport records
`['transport', verb, args]`. One list, `owner`, holds both kinds of owner call.
- **Positive control.** The owner signs in and posts `/api/bus/message`. Exactly
`['option', 'send']` must be recorded, so the route reaches `send` and the count works.
- **The check.** Alice signs in and walks G4's owner routes, copied, plus two chat reads. No answer
may say `owner-only`, so the gate really is open. After each route, every owner record must be
`['transport', 'plugins', {}]`, the read mos-1394 allowed. The message route must reach the user
transport (`message.send`), and every transport opened is alice's.
Why a new file: granted-user.test.mjs's setup imports startServer statically. Passing the gate-open
copy into it would change that fixture, and a fixture change goes to Mos. The route list and setup
are copied, with a comment naming their source.
## Mutant
M12: serve.mjs:689 drops `send` from the user-session destructure, so a user session keeps the
owner's `send`. Killed when "R6-N1" reports not ok. Expected failure: on `POST /api/bus/message`,
the owner record holds `['option', 'send']`. run1/mutants.py applies it (anchor exactly once), runs
the new file alone, restores with git checkout and checks the tree is clean.
## Gate
Duckworth approves the manifest. On Sage's CLEAR: run1/gate.sh does npm ci (root and webui), the new
file whole, the webui suite whole, then M12. Logs go to /mnt/storage/scratch/launchpad-r300/run1,
outside the tree. Not run yet.
MANIFEST.sha256 lists the one new path.
## Run 1 (sage-3408 CLEAR, 12:05Z)
npm ci root and webui were green. The new file was red on its first run (mos-1272, placed by me):
`TypeError: Cannot read properties of undefined (reading 'set-cookie')` in signIn. When I cut down
granted-user.test.mjs's `call`, I dropped `headers: res.headers` from what it resolves, so the
sign-in found no cookies. The fix puts `headers` back, the same as granted-user.test.mjs:40. No
assertion changed. The new file then passed alone (1/0). The fix is a new commit, and the manifest
is re-hashed. The gate resumes at new-file on the new head.
Run 1 resumed (TAG -r2) on db5122920, manifest db8be52ce895603e687985acfae29e53e3e8782ae945c3cded6cca36141bfa3b:
- new file whole: 1 pass, 0 fail.
- webui suite whole: 375 pass, 0 fail, 0 skipped, 0 cancelled, 58.2 s (limit 600).
- M12 killed. "R6-N1" reports not ok at :151: `POST /api/bus/message: ["option","send"]`, where
`["transport","plugins",{}]` is expected. The restore left the tree clean.
Duckworth N1 (duckworth-0263, non-blocking): the setup passes no `upstream`, so the swap's
`upstream = null` has no recorder here. The test proves the swap for the options it stubs. It
proves nothing about `upstream`.
@@ -0,0 +1 @@
03b5d60f68ea2bddd779e1df19519855a5f01cc28ec9ddb26ee7c45fcfe75cf4 packages/webui/tests/user-option-swap.test.mjs
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Row 300 gate run 1 (brief R6-N1): npm ci (root and packages/webui), the new test file whole, the
# webui suite whole, then mutant M12. Stops at the first red. FROM=<step> resumes at a step.
set -u -o pipefail
WT=/mnt/storage/scratch/launchpad-r300/wt
OUT=$WT/agents/launchpad/work/queue-300/run1
LOG=/mnt/storage/scratch/launchpad-r300/run1
FROM=${FROM:-npm-root}; TAG=${TAG:-}
mkdir -p $LOG
export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/gXXXXXX)
echo "start $(date -u +%H:%M:%SZ) FROM=$FROM TAG=$TAG TMPDIR $TMPDIR head $(git -C $WT rev-parse HEAD)" >> $LOG/summary.txt
[ -z "$(git -C $WT status --porcelain)" ] || { echo "STOP worktree not clean" | tee -a $LOG/summary.txt; exit 9; }
on=0
step() { # name limit dir cmd...
local name=$1 limit=$2 dir=$3; shift 3
[ $on = 1 ] || [ $name = $FROM ] || return 0; on=1
local t0=$(date +%s%N)
( cd $dir && timeout -k 10 $limit "$@" ) > $LOG/$name$TAG.log 2>&1
local rc=$?
local ms=$(( ($(date +%s%N) - t0) / 1000000 )); local dt=$((ms/1000)).$(printf %03d $((ms%1000)))
echo "$name$TAG rc=$rc seconds=$dt limit=$limit $(date -u +%H:%M:%SZ)" | tee -a $LOG/summary.txt
[ $rc -eq 0 ] || { echo "STOP at $name$TAG" | tee -a $LOG/summary.txt; exit $rc; }
}
step npm-root 900 $WT npm ci --ignore-scripts
step npm-webui 900 $WT/packages/webui npm ci --ignore-scripts
step new-file 300 $WT/packages/webui node --test tests/user-option-swap.test.mjs
step webui 600 $WT/packages/webui node --test tests/
[ -z "$(git -C $WT status --porcelain)" ] || { echo "STOP worktree dirty after the suite" | tee -a $LOG/summary.txt; git -C $WT status --porcelain >> $LOG/summary.txt; exit 9; }
echo "SUITES GREEN $(date -u +%H:%M:%SZ)" | tee -a $LOG/summary.txt
python3 -B $OUT/mutants.py
rc=$?; echo "MUTANTS rc=$rc $(date -u +%H:%M:%SZ)" | tee -a $LOG/summary.txt; exit $rc
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
# Row 300 mutant M12 (BUILD.md "Mutant"): serve.mjs keeps the owner's send for a user session. The
# edit must match exactly once; the new file runs alone under timeout -k 10 300 with the TAP
# reporter; git checkout restores and the tree must be clean. Killed = "R6-N1" reports "not ok".
import json, os, re, subprocess, sys, time
WT = '/mnt/storage/scratch/launchpad-r300/wt'
LOG = '/mnt/storage/scratch/launchpad-r300/run1'
SWAP = "({ bus, send, settingsWrite, projectFiles, projectFile, projectSave, chat, nameWrite, assistantStop, instancesWrite, routesWrite, agentWrite, processing } = userOptions(session));"
MUTANTS = [
('M12', 'packages/webui/src/serve.mjs', SWAP,
"({ bus, settingsWrite, projectFiles, projectFile, projectSave, chat, nameWrite, assistantStop, instancesWrite, routesWrite, agentWrite, processing } = userOptions(session));",
('packages/webui', 'tests/user-option-swap.test.mjs'), 'R6-N1'),
]
def git(*a):
return subprocess.run(['git', '-C', WT, *a], capture_output=True, text=True, check=True).stdout
def main():
if git('status', '--porcelain'): raise SystemExit('worktree not clean at start')
summary, bad = [], 0
for mid, path, old, new, (pkg, test), killer in MUTANTS:
p = os.path.join(WT, path)
try:
s = open(p).read()
if s.count(old) != 1: raise SystemExit(f'{mid}: anchor count {s.count(old)} in {path}')
open(p, 'w').write(s.replace(old, new))
diff = git('diff')
t0 = time.time()
r = subprocess.run(['timeout', '-k', '10', '300', 'node', '--test', '--test-reporter=tap', test],
cwd=os.path.join(WT, pkg), capture_output=True, text=True)
dt = round(time.time() - t0, 1)
finally:
git('checkout', '--', path)
if git('status', '--porcelain'): raise SystemExit('worktree not clean after restore')
open(f'{LOG}/{mid}.diff', 'w').write(diff)
open(f'{LOG}/{mid}.tap', 'w').write(r.stdout + '\n# stderr\n' + r.stderr)
res = {m.group(2): m.group(1) == 'ok' for m in re.finditer(r'^(ok|not ok) \d+ - (.*)$', r.stdout, re.M)}
hit = [n for n in res if n.startswith(killer)]
killed = r.returncode not in (0, 124, 137) and bool(hit) and all(not res[n] for n in hit)
bad += not killed
print(f'{mid} rc={r.returncode} seconds={dt} killed={killed} red={[n for n, ok in res.items() if not ok]}', flush=True)
summary.append({'mutant': mid, 'rc': r.returncode, 'seconds': dt, 'killed': killed, 'killer': killer})
json.dump(summary, open(f'{LOG}/mutants.json', 'w'), indent=1)
sys.exit(1 if bad else 0)
main()
+67
View File
@@ -0,0 +1,67 @@
# Row 301 build notes (Launchpad): R6-N4, fold user-bus.mjs back into bus.mjs (#1808)
Brief: docs/plans/2026-10-23_r6-followup-rows.md § Brief: R6-N4. Reviewer Duckworth, gate owner Sage.
Base e551cd846 (refactor after row 303). Branch launchpad/row301.
## Change
- packages/webui/src/bus.mjs: the user transport moves in beside humanCall, after `busReader`:
`USER_CLI`, `userCall`, `busBusiness` and `userCaller`, byte for byte from user-bus.mjs (checked
with diff), under a three-line header. One import is added, `fileURLToPath` from node:url.
`readHostState`, `spawn`, `randomBytes` and `BusReadError` were already in bus.mjs. humanCall and
busCaller are untouched.
- packages/webui/src/user-bus.mjs: deleted.
- packages/webui/src/cli.mjs: the two imports become one, from './bus.mjs'.
- packages/webui/src/console-bus.mjs: a comment names bus.mjs instead of user-bus.mjs.
- packages/webui/tests/granted-user.test.mjs:22-23: the two imports become one, from '../src/bus.mjs'.
Only the import path changes. Mos ruled (mos-1449, via sage-3399) that this is covered by "update
its imports" and is not an old-test change.
No other reference to user-bus.mjs remains under packages/. tools/agent-watch's "user-bus" means
something unrelated. The 294 packet's mutants name the old file as history only.
## Re-pin
channel-proof.test.mjs:28 pinned packages/webui/src/bus.mjs at 15e8123f…3313. After the fold the
file hashes to 39fcb27c82e917ef05e1a7a650d522c9f3c656bf8dbade87b8ddb3661b67ad6c. A pin change is an
old-test change. Mos approved it under mos-1272 in mos-1449 (via sage-3399), as its own commit, with
row 301 making its own re-pin window. Mos checked fd4b8c36e read-only: bus.mjs hashes to 39fcb27c,
its diff removes no lines, user-bus.mjs from `export const USER_CLI` to the end is in it byte for
byte, and nothing else imports user-bus.mjs. The second commit changes that one value and adds one
comment line in the row 165/171/194 style.
## Gate
Duckworth approves the manifest. channel-proof.test, the webui suite and the 294 user-transport
tests (granted-user.test.mjs) run whole and green, on Sage's CLEAR.
MANIFEST.sha256 lists the changed paths. DELETED.txt names the one removed path.
## Run 1 (sage-3408 CLEAR, 12:08Z, head 04a179740)
npm ci root and webui green. channel-proof.test whole 2 pass 0 fail. The webui suite whole went
374 tests, 372 pass, 2 fail, 0 skipped, so the gate stopped. granted-user.test whole, run after the
stop to see whether the stop hid another red: 19 pass 0 fail. No file changed.
Both reds were in old tests: library-cli.test.mjs:10 (C2-04) and thread-claims.test.mjs:54 (row278
Console adapter). Each failed with `SyntaxError: The requested module './bus.mjs' does not provide an
export named 'busBusiness'` at cli.mjs:5. Each test spawns cli.mjs with a registerHooks load hook
that replaces /webui/src/bus.mjs with a fixture exporting only BusReadError and busCaller. Before
the fold, cli.mjs took busBusiness and userCaller from the real user-bus.mjs. Now they come from the
stubbed bus.mjs, so cli.mjs fails to link. cli.mjs calls them only under --sso (:98, :103), and
neither test passes --sso. A URL-keyed stub is not an import, so the "nothing else imports
user-bus.mjs" check and the static reviews missed it. `git grep "webui/src/bus.mjs" --
'packages/*/tests/*'` finds exactly channel-proof, library-cli and thread-claims.
## Fixture change (mos-1451 under mos-1272, via sage-3418)
Mos approved option A with one change and refused option B, a namespace import in cli.mjs. Each
of the two busSource template strings (library-cli.test.mjs:17, thread-claims.test.mjs:58) gets
this appended, and nothing else changes in either file:
export const busBusiness=()=>{throw Error('busBusiness outside --sso')};export const userCaller=()=>{throw Error('userCaller outside --sso')};
The stubs throw rather than return null, so a test that reaches them outside --sso fails loudly.
The text has no backtick, `${` or backslash, so the template literal carries it unchanged. The
manifest goes from five paths to seven. Sage's gate: webui 374/0, channel-proof 2/0,
granted-user 19/0. Any other red stops the gate and goes to Mos.
@@ -0,0 +1 @@
deleted packages/webui/src/user-bus.mjs
@@ -0,0 +1,7 @@
db1f05e312b9bee7ece7df7df78047a2965024109a297cb8a24112135a6e5f04 packages/bus/tests/channel-proof.test.mjs
39fcb27c82e917ef05e1a7a650d522c9f3c656bf8dbade87b8ddb3661b67ad6c packages/webui/src/bus.mjs
0862b2b5bf6511d2c5deca1f318eee643edaee489c0ce8c83e57364f2f54a3d8 packages/webui/src/cli.mjs
653d2087c0796a35c3884b7243d1adf8a78c52950a4924049317063f2d3c0974 packages/webui/src/console-bus.mjs
34b6d052855369c8f7cf1faf8bab3c59ddf96ccd4c42731bd86a4e61e5e70640 packages/webui/tests/granted-user.test.mjs
7e782c2b6b46b6b8b6e58cd3d148bf041d49e8fbf2cbbed51080cdb02fc11d89 packages/webui/tests/library-cli.test.mjs
dd1e62d14a59bb8fe7cd4654dd1de8e518270c36d104ee356e165faee7f95a02 packages/webui/tests/thread-claims.test.mjs
+26
View File
@@ -0,0 +1,26 @@
# Row 303 build notes (Launchpad): R9 shows its output when rehearse makes no directory (#1810)
Brief: docs/plans/2026-10-23_r9-guard-row.md (Vikunja 589). Reviewer Duckworth, gate owner Sage.
Base 80793d824 (refactor after row 294 landed at d3059c2c3). Branch launchpad/row303.
## Change
One added line in packages/cli/tests/stack-release.test.mjs, R9 only, at :2178:
assert.ok(r.dir!==null, r.out+r.err);
It sits right after R9's exit-code check and right before the `includes` check whose message builds
`join(r.dir,'placeholders')`. rehearse() (:2040) sets `dir` to null when the output has no
`rehearsal directory:` line, so the guard fails first and its message carries the rehearsal's
output and stderr, including the FAIL line that stopped it. This is a test-only change to a
pre-existing test, approved under mos-1272 and mos-1316 (mos-1422 point 2). It adds an assertion
and changes none. No other line in R9 or any other test changes. The lines the brief names (:2058,
:2113, :2126, :2267, :2280) put r.dir in a template, where a null prints as `null` and the message
still carries r.out, so they get no guard.
MANIFEST.sha256 lists the one changed path, hashed from the commit that adds this file.
## Gate
Duckworth approves the diff. stack-release.test.mjs runs whole and green on Sage's gate, in a gap.
Not run yet.
@@ -0,0 +1 @@
e936808bb12361acf164b675fe40ead416720dfd48c5d51ea5bfee1e1893cc33 packages/cli/tests/stack-release.test.mjs
+177
View File
@@ -0,0 +1,177 @@
# Row 225 (C2-10b) Console chat: replies, citations, write notices, status line
Owner: Morgana, from sage-3582 (18:53Z 10-23). Base: origin/refactor 45dca69f6. Branch: morgana/row225.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-10b. Design: agents/gosalyn/work/case2/DESIGN.md 7.4,
5.2 (the reply schema and the check result) and the write notice and `library.revert` (about line 286).
## Ruling on the dependency gap (sage-3591)
225 builds against the design contracts. `renderReply` stays pure, and the caller supplies the answers
it can't know. The revert route proxies to the bus verb and shows the refusal code until the verb
exists. Landing order: 225 lands after 217 (C2-06), not after 288 (C2-02). Sage adds `after 217` to
row 225. Before 288 lands, the Revert button gets the bus's refusal and shows its code, as the brief
allows.
## A1. The check result I assume (to 217, Launchpad)
The broker stores one check result with each agent-to-human message the guard passes. The Console reads
it on the message row that `/api/chat/threads/<id>/messages` returns, under the key `check`:
```json
{
"check": {
"citations": [
{ "scope": "canon", "agent": null },
{ "scope": "supporting", "agent": "acme/scribe" },
{ "scope": "web", "agent": null }
]
}
}
```
- `check` is `null` or absent on a message the guard didn't check: a human's message, a free-text reply,
a notice, or a message from before 217.
- `check.citations` has one entry per entry of the body's `citations`, in the same order.
- `scope` is one of `canon`, `playground`, `supporting` and `web`. A url cite is `web`.
- `agent` is the `Mosaic-Agent` trailer value, `<business>/<instance>`, from
`git log -1 --format=%(trailers:key=Mosaic-Agent,valueonly) <commit> -- <path>`, or `null` when the
file's last commit at `commit` carries none. A url cite has `agent: null`. The Console shows the
agent-written label when `agent` is a nonempty string, and names the agent.
The Console trusts nothing in the body for scope or label. A cite with no matching check entry renders
with no badge and no label. It doesn't fall back to a scope it worked out itself.
If 217 uses other names, I change mine to theirs before either of us pushes (sage-3591 item 1).
## A2. The revert verb I assume (to 288, Honker)
- Verb: `library.revert`. A human verb (design M4): from an agent session it refuses `human-required`,
like `library.status` (`broker.mjs:5133`).
- Args: `{ "project": <business project name>, "commit": <40 lowercase hex> }`. The design writes
`library.revert {commit}`. `project` is added to match the other library verbs, and like theirs it is the
key of `business.projects` (`mirror.mjs:31`), which is the chat project's `repo` link, not its UUID. Both are required, and no other key
is allowed (`invalid-request`), as the other library verbs check with `keys(args, ...)`.
- Result: `{ "project", "path", "reverted": <the commit given>, "commit": <the new commit> }`.
- Refusal codes the Console shows by name:
- `library-revert-not-agent`: `commit` has no `Mosaic-Agent` trailer for this business, or isn't
reachable from the served commit.
- `stale-base`: a later commit changed that path.
- `human-required`, `invalid-request`, `project-not-configured`.
- Before 288 lands, the bus refuses the verb (expected `unknown-verb`, `broker.mjs:6578`), and the Console
shows that code.
- HTTP status in the Console (`packages/webui/src/bus.mjs` `busStatus`): `stale-base` and
`library-revert-not-agent` map to 409. Every other code keeps the existing mapping.
## A3. The status line
`GET /api/library/status?project=<id>` proxies `library.status {project}` through the existing
`libraryStatus` option. `cli.mjs:110` already passes that option, but `startServer` doesn't read it.
The status line shows:
- the served commit (7 characters) and its age;
- `library-diverged` when the sync state names it;
- the fetch error code;
- the reply limits in force, and the keys that fell back;
- the count of R-problems, linking to the list.
It reads the fields `readSyncState` returns at the base (`packages/library/src/mirror.mjs:232`). It shows
any field 217 adds (the limits) only when present.
## A4. "Changed since" (ruled sage-3596)
Sage ruled that 217 supplies `last`. On a `thread.messages` row that carries `check`, each path cite's
entry gets `last`, the file's last commit at the served tip, read when the message is read. When `last`
is absent the Console marks nothing changed, and a test pins that.
One more field is needed, raised in morgana-0200. The cited `commit` is a library commit, not the
file's own last commit, so a cite made at a commit that didn't touch the file would differ from `last`
while the file is unchanged. I asked 217 to also store `file`, the file's last commit at the cited
commit. That's `%H` from the same `git log -1 <commit> -- <path>` call that reads the trailer, so it
costs no extra git call. The Console marks a cite changed when both `file` and `last` are 40-hex and
differ. The cited commit is reachable from the served one (5.2), so `file` is an ancestor of `last`,
and differing means newer. A missing or malformed `file` or `last` marks nothing.
The entry the Console reads, in full: `{ scope, agent, file?, last? }`.
## A5. `no-cited-answer` retires
`renderItems` and its `NOTICES` (`no-cited-answer`, `no-answer`, `turn-failed`) go. 217 sends the
runner's fixed texts as plain bodies (design 5.2, "Runner texts"), and renderReply shows those as free
text. Design 7.4 names no other notice than `library-write`, so nothing is lost (sage-3596 item 3).
## Files
Both deviations below were accepted in sage-3596 and are named in the manifest for Dewey. The brief owns these:
- `packages/library/src/render.mjs`: `renderReply` replaces `renderItems`.
- `packages/library/src/index.mjs`: the export.
- `packages/library/README.md`: the section.
- `packages/library/tests/render.test.mjs`.
It also owns `bus.js`, its stylesheet, `serve.mjs` (only the two routes) and new webui tests. Two
deviations, both recorded here:
- The chat view's message list is `packages/webui/src/public/chat.js:85` (`window.MosaicChat`), not
`bus.js`. The reply, notice and Revert rendering goes in `chat.js` and `chat.css`. The status line
goes in the chat thread view, so it is also in `chat.js`.
- `packages/webui/src/console-bus.mjs` and `cli.mjs` each get one `libraryRevert` line, beside the
existing `libraryStatus` wiring.
## Overlap with row 220 (C2-10a, Researcher, 3ef6da90d)
220 changes one line each in the webui tests discord.test.mjs, fixture.mjs, history-fixture.mjs,
relaunch.test.mjs and return-flow.test.mjs. Its other paths are in control-board, conversation, discord
and business. 225 doesn't touch those five files or those packages. Its webui tests are new files.
## Handoff to Fethry (mos-1501, Q245)
The row moved to Fethry (Codex, fethry/row225). Everything below is **untested**: no suite or single file
has run on it, since a run needs Sage's CLEAR. Each changed file passes `node --check`.
Done in the handoff commit:
- `render.mjs` `renderReply(body, {check, changed, name})`; index export; README section;
`render.test.mjs` rewritten. `renderItems` is gone from every package.
- `serve.mjs`: `/render.mjs` static entry (owner and user file maps), `GET /api/library/status`
(one `project` query, answer must name that project), `POST /api/library/revert` (save's boundary,
strict `{project, commit}`, refusal shows the code, lost or unusable answer is `outcome-unknown`,
never retried). Both are nulled for a granted user, and `userRoute` keeps them owner-only. The tester
listener refuses `libraryRevert`.
- `bus.mjs`: `library-revert-not-agent: 409`. That re-pins `packages/webui/src/bus.mjs` in
`packages/bus/tests/channel-proof.test.mjs`, with a comment line, as row 254 did. It's a third deviation,
in another package's test.
- `console-bus.mjs` and `cli.mjs`: `libraryRevert`, beside `libraryStatus`.
- `chat.js`: dynamic `import('/render.mjs')` (CSP is `script-src 'self'`); body text until it loads.
Cites with a scope badge, the agent-written label, "changed since" from `file`/`last`, links only for
https with `rel="noopener noreferrer"`. A notice gets a Revert button behind `confirm()`. The library
status line goes under the thread heading.
- `chat.css`: cite, badge, notice and status styles.
- New tests: `packages/webui/tests/library-routes.test.mjs` and
`packages/webui/tests/chat-replies-browser.test.mjs`. 220's five files are untouched.
Decisions made in the build:
- The library name is the chat project's `repo`. A project with no repo reads no status and its
Revert does nothing.
- These codes hide the status line: `project-not-declared`, `library-mapping-invalid`,
`project-not-configured`, `not-configured` and `owner-only`.
- Otherwise every code-repo thread, and every granted user, would show "unavailable".
- Cost: a broken library mapping also hides the line. `library.status` from the CLI still shows it.
- "Age" is the time since `lastFetch` ("fetched 5 min ago"), not the served commit's age, which the
status doesn't carry.
- The limits fields are assumed to be `replyLimits {requireCitation, citationsPerReply}` and
`replyLimitsFallback [keys]`, and are shown only when present. **Not agreed with 217.** Confirm with
Beakley and rename.
- A notice body's shape is all the renderer checks. An agent in a business without a library could
send a body that looks like a notice. The page would show it, but `library.revert` still refuses it.
Left:
1. Get CLEAR and run the webui and library suites, `channel-proof.test.mjs`, and the two new files whole.
The browser test's selectors and waits have never run.
2. Agree the limits field names with 217 (Beakley).
3. Granted-user R6 G5 (`granted-user.test.mjs:252`) lists owner verbs by hand. Add `library.revert`
once 288 lands.
4. BUILD.md: the mutant list and the mos-1275 timeout plan.
5. The manifest names the three deviations: chat.js and chat.css in place of bus.js, the console-bus and
cli lines, and the channel-proof re-pin.
+205
View File
@@ -0,0 +1,205 @@
# Row 306: a revoked run's late bus call stops it instead of exiting 22 (#1813, 295 N1)
Owner Morgana. Reviewer Dewey. Gate owner Sage. Brief: docs/plans/2026-10-23_gate-env-rows.md, third
section (c16e943b9). Go: sage-3478 (Mos-1453 puts 306 next). Base: refactor fe4ee8c7b, after row 272
(d33989230) and row 307 (d243f6b25). Every line below is as it stands at fe4ee8c7b.
## Design round 1 (no code yet), round 2 edits marked r2
### The window today
- `#revokeLost` (packages/bus/src/broker.mjs:1012-1029) ends each lost user thread's held claim in
one transaction: `#claimEnd(..., 'revoke', null, 'access-revoked', by)` at :1022. The four
callers are the timer `accessSweep` (:1039), a user's request (:4542), `access.reload` (:4561)
and `threadsState` (:1107 and the access-expiry sweep at :1113).
- The launcher learns of the loss from `threadsState`'s `revoked: true` row, records
`thread-revoked` and sends SIGTERM (packages/cli/src/launcher.mjs:710-718).
- A runner call between that claim end and the signal goes through `#agent` (:1064-1072). The
run's latest claim row is now op `revoke`, so the check at :1069 fails. :1070 answers
`launcher-released` only for a run in `#launcherReleased` (:431, row 302), and `not-holder`
otherwise.
- The runner's `ended()` (packages/harness/src/runner.mjs:488-500) counts `not-holder` as a gone
session (`SESSION_GONE`, :55). It sets `code = EXIT.session` and the run exits 22 with
`session-invalid`.
### D1: the broker remembers the revoked run in memory, the same way as #launcherReleased
- A new field next to `#launcherReleased` (:431): `#accessRevoked = new Set()`, keyed
`business:run`, with a comment naming row 306.
- `#revokeLost` adds each ended claim's `h.business + ':' + h.holder_run`. It adds them after the
transaction at :1018-1026 commits, by iterating `ended`, so a transaction that throws leaves the
Set unchanged. This is the only place the Set grows. No other `#claimEnd` caller adds to it:
`assistant.stop` (:6078), `role.revoke`/`role.end` (:5333, :5338), `instance.fire` (:4780), the
limit stop (:4949), `threadIdle` (:1133) and `landRelease` do not.
- `#agent`'s refusal at :1070 becomes three-way, and only on the existing failure branch:
`launcher-released` if the run is in `#launcherReleased`, else `thread-revoked` if it is in
`#accessRevoked`, else `not-holder`. A run that still holds its claim never reaches the branch.
The two sets don't overlap. A released run holds no claim for the sweep to end, and a revoked run
holds none for `threadIdle` or `landRelease` to release (both refuse `not-holder` first, :1131 and
:4237).
- **Where the memory ends.** `endLaunch` deletes the key next to `#launcherReleased.delete(key)` at
:1394. That is also where every capability of the run is dropped (:1395-1396), so after it no call
of the run reaches `#agent`.
- **The alternative I didn't take.** Derive the answer from the store: the run's latest claim row is
op `revoke` with reason `access-revoked`. It would survive a broker restart. But `reason` is
caller text at :4780, :5333 and :5338, so a human's `role.revoke` with reason `access-revoked`
would turn that run's `not-holder` into `thread-revoked`, a stopped exit. `by` doesn't separate the
cases either: `access.reload` passes the human's name (:4561). The Set can't be spoofed that way,
and it matches row 302's shape. The cost is N1 below.
- **Unchanged.** `role.claim` keeps its order: `launcher-released` (:5252), then `run-revoked` for a
run with any revoke row in its session (:5257-5258), then `thread-revoked` from the file (:5259).
`bindLaunch` keeps refusing `thread-revoked` for a new run of a lost thread (:1371). The limit
stop's `not-holder` (:4952) is unchanged.
### D2: the runner maps thread-revoked from a call to stopped
- runner.mjs gets `THREAD_REVOKED = "thread-revoked"` next to `LAUNCHER_RELEASED` (:58), with a
comment naming row 306.
- In `ended()` (:491-500), `thread-revoked` takes the launcher-released branch:
- it logs its own line, `runner: thread revoked by the access file`;
- it sets `released = true` and returns true;
- `code` stays `EXIT.stopped`.
`released` also skips the exit-time `role.release` (:596-602), which is right because the sweep
already ended the claim.
- The refusal of the run's first `role.claim` (:459-467) is unchanged. `thread-revoked` there stays
`EXIT.claim` (21), because the brief maps it only "from a call" after the claim. Mostly
`bindLaunch` refuses such a run before it starts.
- `ended()` covers every call site in the loop: landing `state` (:521), `message.receive` (:543),
the turn's `state` (:561) and the reply's `message.send`/`message.read` (:589). The harness's tool
calls go through the tool server and return their error to the harness, as they do for
`launcher-released` today. The run stops at its next own call.
### D3: docs
- packages/bus/README.md:328: "the run's next bus call refuses `not-holder`" becomes
`thread-revoked`, until the run ends (row 306).
- packages/harness/README.md:296: exit 0 adds `thread-revoked` next to `launcher-released`.
- The `#revokeLost` comment at broker.mjs:1008 changes the same way as the bus README.
## Tests (red first under mos-1272)
### New file packages/bus/tests/revoke-window.test.mjs
It uses access-revoke.test.mjs's fixture shape, copied, because that file is an old test.
- **T1.** A run the timer sweep ended answers `thread-revoked` on `message.receive`, `state`,
`message.read` and `message.send`, and each answer is logged as `action.refused` with code
`thread-revoked`. Its `role.claim` still refuses `run-revoked`. The same holds when the trigger is
a user's request or `access.reload`, one assert each, because all three go through `#revokeLost`.
- **T2, the control.** In the same broker, after the sweep ended the first run:
- a second user thread's run whose claim the human's `assistant.stop` ended answers `not-holder`;
- a third run with a pending limit stop answers `not-holder` on its first `message.receive`, and
then `not-holder` again on its next call (r2, Dewey C2). The first answer comes from
`#limitStop` (:4952) and never reaches `#agent`, so only the second call checks the Set.
### New file packages/cli/tests/revoke-window.test.mjs
thread-revoke.test.mjs's `start()`, copied the way idle-stop-race.test.mjs copied thread-launch's,
with the `threadsState` op replaced by the caller.
- **T3, the forcing test.**
- Setup: a user thread A owned by alice and a human-owned thread B. Both get a message and a
RESULT, so both runs hold.
- Trigger: a hand edit drops alice's grant.
- The hold: the replaced `threadsState` op calls `runtime.threadsState`. That call's sweep ends A's
claim. When the rows include A as `revoked`, and before returning them to the launcher, the op
has the human `assistant.stop` the instance's human-owned threads (B), then waits. It waits until
A's runner has written its verdict line and the launch log has A's `end`, then the same for B.
The launcher doesn't tick while a pass is running, so nothing signals either run while the op
is held.
- Asserts for A:
- the verdict is `thread revoked by the access file`;
- the end is `["stopped", 0, null]`;
- the launch log has no `thread-revoked` event for A's run (r2, Dewey C1). The launcher
removes A from the registry before it records `end` (packages/cli/src/launcher.mjs:643-646),
so when the held op returns, `registry.running` at :705 finds nothing and :711-717 record
and signal nothing. The runner stopped by itself before any SIGTERM;
- the runner log has no `session stopped working` and no `role.release:`, and ends with
`exiting 0`;
- the store's `action.refused` codes for A include `thread-revoked` and not `not-holder`.
- Asserts for B, the control:
- the verdict is `session stopped working: not-holder`;
- the end is `["session-invalid", 22, null]`;
- B's codes include `not-holder`.
- Every wait returns what it last saw, so a mutant fails on an assertion, not a timeout
(sage-2707).
## Mutants
| # | Edit | Must turn red |
|---|------|---------------|
| M1 | `#agent` drops the `#accessRevoked` check: the failure answers `launcher-released` or `not-holder` | T1, T3 (A gets 22) |
| M2 | runner `ended()` maps `thread-revoked` to `EXIT.session` (it moves into `SESSION_GONE`) | T3 (A exits 22) |
| M3 | every ended thread claim marks its run revoked: `#claimEnd` adds `s.business:h.holder_run` to `#accessRevoked` when `h.thread` (anchor `if (h.thread)`, one hit at :6495) | T2 (both halves, the limit half on its second call), T3 (B gets thread-revoked) |
Each anchor must hit exactly once. The kit prints the count, as row 295's M8 receipt did.
## Expected first-run reds
- **New tests (mos-1272).** On a red-first run against fe4ee8c7b, T1 and T3 are red: A gets
`not-holder` and exits 22. T2 is green on both sides. Any other first-run red in T1-T3 I place
myself and report after.
- **Old test that changes: Mos GO (mos-1463, via sage-3483).** In
packages/bus/tests/access-revoke.test.mjs (row 295), `not-holder` becomes `thread-revoked` in the
six asserts at :103, :120, :140, :171, :201 and :262, and the :95 title's "refuses not-holder"
becomes "refuses thread-revoked". Nothing else in the file changes; `git diff --numstat` for it
reads `7 7`. Dewey checks that in the manifest round. At fe4ee8c7b those are the only seven
`not-holder` lines in the file.
- **Checked and not expected to change.**
- thread-revoke.test.mjs:180 asserts `["stopped", 0, null]` for a run that only the SIGTERM ends.
If its runner's poll happened to land in the window, it would get exit 0 now, where today it
would get 22, so the assert holds either way.
- idle-stop-race.test.mjs's control is a human `assistant.stop`, not a revoke, so it stays
`not-holder`/22. M3 would turn it red too.
- A `git grep` of `packages/*/tests/*` finds `not-holder` after a revoke only in
access-revoke.test.mjs.
## Gate (per the brief)
Dewey approves this design, then the manifest. On Sage's gate (mos-1455 D4, sage-3483/3484):
- the new files and access-revoke.test.mjs whole;
- the bus, cli, plugins, webui, business and harness suites;
- every `scripts/test-*.sh` under `timeout -k 10` at three times its longest run (mos-1275);
- M1-M3.
Every suite shows 0 failures. Any red follows mos-1272.
## N items
Both stay out of this row (sage-3483); Sage adds them to DEFERRED at landing. Dewey agrees on N1
(dewey-0918).
- N1. The Set is lost on a broker restart. recover() rebinds an earlier host's run (:1366-1373),
and if that run makes a call before the launcher's SIGTERM, it gets `not-holder` and exits 22, as
every revoked run does today. Seeding the Set from the store at a rebind would need a mark only
`#revokeLost` writes (D1's alternative shows `reason` and `by` aren't one). I'd leave it unless
Dewey wants that mark.
- N2. A revoked run that also has a pending limit stop gets `#limitStop`'s `not-holder` (:4572-4573,
:4952) on `message.receive` and `state`, because that check runs before `#agent`. It's rare; I'd
leave it and record it.
## Round 2 (dewey-0918 CHANGES C1-C3, sage-3483)
- C1: T3 asserts A has no `thread-revoked` launch-log event, in place of "end before the event".
- C2: T2's limit-stopped run makes a second call, which carries M3.
- C3: the gate lists the six suites and every scripts/test-*.sh with 0 failures; mos-1463's
bounds for access-revoke.test.mjs are in "Expected first-run reds".
- Cosmetic: threadIdle's `#claimEnd` is :1133.
## Build (dewey-0920 APPROVE r2, sage-3487)
- Red-first commit d38c5213a: packages/bus/tests/revoke-window.test.mjs (T1 two tests, T2) and
packages/cli/tests/revoke-window.test.mjs (T3), no product code.
- Build commit (this one): D1 in broker.mjs, D2 in runner.mjs, D3 in the two READMEs and the
#revokeLost comment, and the mos-1463 edit to access-revoke.test.mjs (numstat 7 7).
- Nothing has run yet; every run is slot work.
Expected first-run reds, on d38c5213a (new tests, fe4ee8c7b product):
- bus revoke-window: both T1 tests red, A answers `not-holder` where `thread-revoked` is expected.
T2 green.
- cli revoke-window T3 red: A's verdict is `session stopped working: not-holder`, its end
`session-invalid`/22.
- access-revoke.test.mjs is unchanged at d38c5213a, so it stays green there.
On the build commit every file above, and every gate suite, is expected green.
+49
View File
@@ -0,0 +1,49 @@
# Row 304 / Vikunja 590 / Gitea #1811
Owner Rocko; reviewer Dewey; gate owner Sage. Base c16e943b90ef71ac96cfa5314bbea6040634d14d.
Brief docs/plans/2026-10-23_gate-env-rows.md first section, approved exact
one-line old-test change under mos-1442 point 1 / sage-3362.
265 is queued and has no active build, so 304 goes first. Slot requested;
no Node, test, queue claim or push before Sage CLEAR.
Before the edit, run the unchanged whole claude-session.test.mjs with
fixture-only HOME, CLAUDE_CONFIG_DIR and scripted API under an underscore
TMPDIR and a hyphen TMPDIR. Expected: underscore 9/1 only at the unrestricted
memory control (:298), hyphen 10/0. Record both raw results here. Stop for
Sage if the installed CLI's mapping differs from [^A-Za-z0-9] -> '-'.
Only then replace /[/.]/g with /[^A-Za-z0-9]/g on the slug line. Every
assertion and every other test line stays byte-identical. No product change.
Acceptance is whole file green under mktemp, underscore, and space TMPDIR
(if the CLI accepts space); old-regex mutant must fail the :298 assertion
under underscore. Run scope and claim wait for Sage's slot grant.
Use short fixture TMPDIRs under /mnt/storage/scratch/tmp, process groups,
explicit TAP and bounded runs. No Docker, browser, suites or live state.
Keep token/config paths and real provider access out of every fixture.
## Evidence, sage-3366 (mos-1442 point 1)
queue next selected304; claim rev3521, rc0. Base test unchanged until both
mapping probes finished. Correct restricted-check citation: :293, not :291.
| Run | TMPDIR | Exit | Pass/fail/skip |
|---|---|---|---|
| baseline-underscore | /mnt/storage/scratch/tmp/rk304_OV81Op | 1 | 9/1/0 |
| baseline-hyphen | /mnt/storage/scratch/tmp/rk304-UnWJbm | 0 | 10/0/0 |
| fixed-mktemp | /mnt/storage/scratch/tmp/rk304yw6LWT | 0 | 10/0/0 |
| fixed-underscore | /mnt/storage/scratch/tmp/rk304_MyhpSs | 0 | 10/0/0 |
| fixed-space | /mnt/storage/scratch/tmp/rk304 i5d7HA | 0 | 10/0/0 |
| M1-old-regex | /mnt/storage/scratch/tmp/rk304_595gfj | 1 | 9/1/0 |
Baseline underscore and M1 both fail only the named unrestricted positive
control at claude-session.test.mjs:298:10, ERR_ASSERTION, missing MARKER-MEMORY.
Hyphen baseline is green. This confirms the expected installed-CLI mapping.
Space is accepted and passes after the fix. All3 fixed whole-file runs30/0/0;
baseline hyphen10/0/0; M1 one assertion kill (whole file9/1/0).0cancellations,
0skips throughout. Tests11:05:01Z-11:05:36Z, no unexpected red or retry.
Every other source byte, including all assertions, is unchanged, recorded in
assertion-preservation.json. Fixed test sha2567690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9.
Cwd node/chromium/claude sweep empty; no Docker/browser/suite/live activity.
Sage's separate gate and Dewey's review still required; no gate claimed here.
@@ -0,0 +1,6 @@
{
"allOtherBytesUnchanged": true,
"assertionLinesUnchanged": true,
"baseSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0",
"candidateSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9"
}
+1
View File
@@ -0,0 +1 @@
c16e943b90ef71ac96cfa5314bbea6040634d14d
+13
View File
@@ -0,0 +1,13 @@
diff --git a/packages/harness/tests/claude-session.test.mjs b/packages/harness/tests/claude-session.test.mjs
index bf5cb2d29..d9c45e446 100644
--- a/packages/harness/tests/claude-session.test.mjs
+++ b/packages/harness/tests/claude-session.test.mjs
@@ -269,7 +269,7 @@ test("claude adapter: a directory pin exits 2 before it runs; a pin with a space
test("claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do", { skip }, async (t) => {
const { dir, workspace, env } = await session(t, []);
- const slug = realpathSync(workspace).replace(/[/.]/g, "-");
+ const slug = realpathSync(workspace).replace(/[^A-Za-z0-9]/g, "-");
const plant = {
"MARKER-USER": [join(env.HOME, ".claude", "CLAUDE.md"), join(env.CLAUDE_CONFIG_DIR, "CLAUDE.md")],
"MARKER-PARENT": [join(dir, "CLAUDE.md")],
@@ -0,0 +1 @@
7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9 packages/harness/tests/claude-session.test.mjs
@@ -0,0 +1 @@
packages/harness/tests/claude-session.test.mjs
@@ -0,0 +1 @@
[]
+14
View File
@@ -0,0 +1,14 @@
START baseline-underscore 2026-10-23T11:05:01.371651+00:00 /mnt/storage/scratch/tmp/rk304_OV81Op
END {"label": "baseline-underscore", "start": "2026-10-23T11:05:01.371651+00:00", "end": "2026-10-23T11:05:06.998843+00:00", "seconds": 5.627, "exit": 1, "counts": {"tests": 10, "pass": 9, "fail": 1, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304_OV81Op", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0", "skips": [], "log": "agents/rocko/work/queue-304/logs/baseline-underscore.tap"}
START baseline-hyphen 2026-10-23T11:05:07.003823+00:00 /mnt/storage/scratch/tmp/rk304-UnWJbm
END {"label": "baseline-hyphen", "start": "2026-10-23T11:05:07.003823+00:00", "end": "2026-10-23T11:05:12.829873+00:00", "seconds": 5.826, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304-UnWJbm", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0", "skips": [], "log": "agents/rocko/work/queue-304/logs/baseline-hyphen.tap"}
START fixed-mktemp 2026-10-23T11:05:12.834143+00:00 /mnt/storage/scratch/tmp/rk304yw6LWT
END {"label": "fixed-mktemp", "start": "2026-10-23T11:05:12.834143+00:00", "end": "2026-10-23T11:05:18.560124+00:00", "seconds": 5.726, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304yw6LWT", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9", "skips": [], "log": "agents/rocko/work/queue-304/logs/fixed-mktemp.tap"}
START fixed-underscore 2026-10-23T11:05:18.564553+00:00 /mnt/storage/scratch/tmp/rk304_MyhpSs
END {"label": "fixed-underscore", "start": "2026-10-23T11:05:18.564553+00:00", "end": "2026-10-23T11:05:24.340312+00:00", "seconds": 5.776, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304_MyhpSs", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9", "skips": [], "log": "agents/rocko/work/queue-304/logs/fixed-underscore.tap"}
START fixed-space 2026-10-23T11:05:24.344312+00:00 /mnt/storage/scratch/tmp/rk304 i5d7HA
END {"label": "fixed-space", "start": "2026-10-23T11:05:24.344312+00:00", "end": "2026-10-23T11:05:30.170101+00:00", "seconds": 5.826, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304 i5d7HA", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9", "skips": [], "log": "agents/rocko/work/queue-304/logs/fixed-space.tap"}
START M1-old-regex 2026-10-23T11:05:30.173937+00:00 /mnt/storage/scratch/tmp/rk304_595gfj
END {"label": "M1-old-regex", "start": "2026-10-23T11:05:30.173937+00:00", "end": "2026-10-23T11:05:36.100361+00:00", "seconds": 5.926, "exit": 1, "counts": {"tests": 10, "pass": 9, "fail": 1, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304_595gfj", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0", "skips": [], "log": "agents/rocko/work/queue-304/logs/M1-old-regex.tap"}
BUILD_GREEN 2026-10-23T11:05:36.103508+00:00
CWD_SWEEP []
@@ -0,0 +1,99 @@
TAP version 13
# Subtest: claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
ok 1 - claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
---
duration_ms: 810.176546
type: 'test'
...
# Subtest: claude: the hook alone blocks a path outside the workspace
ok 2 - claude: the hook alone blocks a path outside the workspace
---
duration_ms: 503.543336
type: 'test'
...
# Subtest: claude: a second turn resumes the first turn's session
ok 3 - claude: a second turn resumes the first turn's session
---
duration_ms: 915.865915
type: 'test'
...
# Subtest: claude adapter: --restricted is always passed
ok 4 - claude adapter: --restricted is always passed
---
duration_ms: 4.807726
type: 'test'
...
# Subtest: claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
ok 5 - claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
---
duration_ms: 15.271577
type: 'test'
...
# Subtest: claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
ok 6 - claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
---
duration_ms: 5.309932
type: 'test'
...
# Subtest: claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
not ok 7 - claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
---
duration_ms: 888.601244
type: 'test'
location: '/mnt/storage/scratch/rocko-r304/packages/harness/tests/claude-session.test.mjs:270:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
'MARKER-USER',
'MARKER-PARENT',
'MARKER-WS',
- 'MARKER-MEMORY'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'MARKER-USER'
1: 'MARKER-PARENT'
2: 'MARKER-WS'
3: 'MARKER-MEMORY'
actual:
0: 'MARKER-USER'
1: 'MARKER-PARENT'
2: 'MARKER-WS'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r304/packages/harness/tests/claude-session.test.mjs:298:10)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: claude: a missing hook or MCP file refuses before claude starts
ok 8 - claude: a missing hook or MCP file refuses before claude starts
---
duration_ms: 9.250346
type: 'test'
...
# Subtest: claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
ok 9 - claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
---
duration_ms: 1764.786892
type: 'test'
...
# Subtest: claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
ok 10 - claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
---
duration_ms: 871.328606
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 9
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5870.59342
@@ -0,0 +1,70 @@
TAP version 13
# Subtest: claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
ok 1 - claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
---
duration_ms: 780.047981
type: 'test'
...
# Subtest: claude: the hook alone blocks a path outside the workspace
ok 2 - claude: the hook alone blocks a path outside the workspace
---
duration_ms: 503.370776
type: 'test'
...
# Subtest: claude: a second turn resumes the first turn's session
ok 3 - claude: a second turn resumes the first turn's session
---
duration_ms: 848.822885
type: 'test'
...
# Subtest: claude adapter: --restricted is always passed
ok 4 - claude adapter: --restricted is always passed
---
duration_ms: 4.629499
type: 'test'
...
# Subtest: claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
ok 5 - claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
---
duration_ms: 15.371187
type: 'test'
...
# Subtest: claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
ok 6 - claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
---
duration_ms: 5.545444
type: 'test'
...
# Subtest: claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
ok 7 - claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
---
duration_ms: 947.229815
type: 'test'
...
# Subtest: claude: a missing hook or MCP file refuses before claude starts
ok 8 - claude: a missing hook or MCP file refuses before claude starts
---
duration_ms: 8.467575
type: 'test'
...
# Subtest: claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
ok 9 - claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
---
duration_ms: 1738.629452
type: 'test'
...
# Subtest: claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
ok 10 - claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
---
duration_ms: 857.865121
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5790.544695
@@ -0,0 +1,100 @@
TAP version 13
# Subtest: claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
ok 1 - claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
---
duration_ms: 748.693342
type: 'test'
...
# Subtest: claude: the hook alone blocks a path outside the workspace
ok 2 - claude: the hook alone blocks a path outside the workspace
---
duration_ms: 499.864544
type: 'test'
...
# Subtest: claude: a second turn resumes the first turn's session
ok 3 - claude: a second turn resumes the first turn's session
---
duration_ms: 829.415272
type: 'test'
...
# Subtest: claude adapter: --restricted is always passed
ok 4 - claude adapter: --restricted is always passed
---
duration_ms: 4.710643
type: 'test'
...
# Subtest: claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
ok 5 - claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
---
duration_ms: 14.085981
type: 'test'
...
# Subtest: claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
ok 6 - claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
---
duration_ms: 4.998474
type: 'test'
...
# Subtest: claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
not ok 7 - claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
---
duration_ms: 863.150735
type: 'test'
location: '/mnt/storage/scratch/rocko-r304/packages/harness/tests/claude-session.test.mjs:270:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
'MARKER-USER',
'MARKER-PARENT',
'MARKER-WS',
- 'MARKER-MEMORY'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'MARKER-USER'
1: 'MARKER-PARENT'
2: 'MARKER-WS'
3: 'MARKER-MEMORY'
actual:
0: 'MARKER-USER'
1: 'MARKER-PARENT'
2: 'MARKER-WS'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r304/packages/harness/tests/claude-session.test.mjs:298:10)
process.processTicksAndRejections (node:internal/process/task_queues:104:5)
async Test.run (node:internal/test_runner/test:1409:7)
async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
...
# Subtest: claude: a missing hook or MCP file refuses before claude starts
ok 8 - claude: a missing hook or MCP file refuses before claude starts
---
duration_ms: 8.695573
type: 'test'
...
# Subtest: claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
ok 9 - claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
---
duration_ms: 1671.315421
type: 'test'
...
# Subtest: claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
ok 10 - claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
---
duration_ms: 823.801717
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 9
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5552.702962
@@ -0,0 +1,70 @@
TAP version 13
# Subtest: claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
ok 1 - claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
---
duration_ms: 822.315273
type: 'test'
...
# Subtest: claude: the hook alone blocks a path outside the workspace
ok 2 - claude: the hook alone blocks a path outside the workspace
---
duration_ms: 473.785584
type: 'test'
...
# Subtest: claude: a second turn resumes the first turn's session
ok 3 - claude: a second turn resumes the first turn's session
---
duration_ms: 834.093184
type: 'test'
...
# Subtest: claude adapter: --restricted is always passed
ok 4 - claude adapter: --restricted is always passed
---
duration_ms: 4.587717
type: 'test'
...
# Subtest: claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
ok 5 - claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
---
duration_ms: 15.598032
type: 'test'
...
# Subtest: claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
ok 6 - claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
---
duration_ms: 5.369426
type: 'test'
...
# Subtest: claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
ok 7 - claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
---
duration_ms: 831.362199
type: 'test'
...
# Subtest: claude: a missing hook or MCP file refuses before claude starts
ok 8 - claude: a missing hook or MCP file refuses before claude starts
---
duration_ms: 8.237071
type: 'test'
...
# Subtest: claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
ok 9 - claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
---
duration_ms: 1687.124498
type: 'test'
...
# Subtest: claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
ok 10 - claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
---
duration_ms: 884.665674
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5663.888591
@@ -0,0 +1,70 @@
TAP version 13
# Subtest: claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
ok 1 - claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
---
duration_ms: 740.530837
type: 'test'
...
# Subtest: claude: the hook alone blocks a path outside the workspace
ok 2 - claude: the hook alone blocks a path outside the workspace
---
duration_ms: 484.250736
type: 'test'
...
# Subtest: claude: a second turn resumes the first turn's session
ok 3 - claude: a second turn resumes the first turn's session
---
duration_ms: 857.991709
type: 'test'
...
# Subtest: claude adapter: --restricted is always passed
ok 4 - claude adapter: --restricted is always passed
---
duration_ms: 5.19059
type: 'test'
...
# Subtest: claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
ok 5 - claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
---
duration_ms: 16.380045
type: 'test'
...
# Subtest: claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
ok 6 - claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
---
duration_ms: 5.47195
type: 'test'
...
# Subtest: claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
ok 7 - claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
---
duration_ms: 869.204192
type: 'test'
...
# Subtest: claude: a missing hook or MCP file refuses before claude starts
ok 8 - claude: a missing hook or MCP file refuses before claude starts
---
duration_ms: 8.011154
type: 'test'
...
# Subtest: claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
ok 9 - claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
---
duration_ms: 1760.816087
type: 'test'
...
# Subtest: claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
ok 10 - claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
---
duration_ms: 951.447392
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5778.476849
@@ -0,0 +1,70 @@
TAP version 13
# Subtest: claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
ok 1 - claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist
---
duration_ms: 766.913918
type: 'test'
...
# Subtest: claude: the hook alone blocks a path outside the workspace
ok 2 - claude: the hook alone blocks a path outside the workspace
---
duration_ms: 496.038436
type: 'test'
...
# Subtest: claude: a second turn resumes the first turn's session
ok 3 - claude: a second turn resumes the first turn's session
---
duration_ms: 839.816577
type: 'test'
...
# Subtest: claude adapter: --restricted is always passed
ok 4 - claude adapter: --restricted is always passed
---
duration_ms: 4.571987
type: 'test'
...
# Subtest: claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
ok 5 - claude adapter: MOSAIC_CLAUDE_BIN wins over PATH and must be an absolute executable
---
duration_ms: 17.17544
type: 'test'
...
# Subtest: claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
ok 6 - claude adapter: a directory pin exits 2 before it runs; a pin with a space runs
---
duration_ms: 5.448032
type: 'test'
...
# Subtest: claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
ok 7 - claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do
---
duration_ms: 873.897762
type: 'test'
...
# Subtest: claude: a missing hook or MCP file refuses before claude starts
ok 8 - claude: a missing hook or MCP file refuses before claude starts
---
duration_ms: 8.282957
type: 'test'
...
# Subtest: claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
ok 9 - claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after
---
duration_ms: 1738.792709
type: 'test'
...
# Subtest: claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
ok 10 - claude: row 66 a broken lists.json gives no clean verdict, the operator is told once, and the session still answers
---
duration_ms: 863.908172
type: 'test'
...
1..10
# tests 10
# suites 0
# pass 10
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 5704.886282
@@ -0,0 +1,16 @@
0b487adf165fb6b83e9cba38fc49167c6ec54b66261d955052f7ee5a877fde07 BUILD.md
a75846e9e3217b524ad09bb423033975da6c44074d8d74dc14193550ec1666b9 assertion-preservation.json
5ddf1af4e2d23bc579e6a4005c2b355794dcc33fbd29c0421a6d1e9ca60f70ec base.txt
85dc6f969a11d933b04cc7b2fe49199d745029779c66bf1815f549c51ec91568 build.patch
a09b498872aaf4f3b3af9b297d839d4ddb6fdc57d7a01f05cc1a48200c30cfd4 candidate-manifest.sha256
8588e890cdfadd6911fd3b473aad3ff826f6dd91a52e8d10aeb4a28f7929d995 changed-paths.txt
37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570 cwd-sweep.json
767c5c04bc6442bb6efc8de3bf86e5ef8d2bbdd90d20d72fd604d18e69290f91 driver.log
f6f8a66ccc0c025782cc6932de9745720ca7574a0376d6122074275332abaf43 logs/M1-old-regex.tap
dc290774a454c85033fdc8c71226f7cd56a07e52106b2868ca91ca5ea652ddca logs/baseline-hyphen.tap
960114150fbf0a3069762d356b6cffe0068208a69e805bb0422de0da8825af35 logs/baseline-underscore.tap
1d0836115cdf344e7cdcbe1ef9de7a3f2e57ca06a4cca42a07adb14ba82bd29a logs/fixed-mktemp.tap
a69191da6fd3eb20ec99b92521df9418e9815b7879a159cc7b2abbd5b790261c logs/fixed-space.tap
9776e07a392bc567609573a66a2a17619ded0863b8ee35cbc53482e42c58220b logs/fixed-underscore.tap
3adeed328506abdeff0bb108ac16d306736e2caba1c8188d48173e5ea75b5c81 results.jsonl
084a848241bf874a5873a881d2c47efd29deff7b6682947fc7738053556845d0 run-build.py
@@ -0,0 +1,6 @@
{"label": "baseline-underscore", "start": "2026-10-23T11:05:01.371651+00:00", "end": "2026-10-23T11:05:06.998843+00:00", "seconds": 5.627, "exit": 1, "counts": {"tests": 10, "pass": 9, "fail": 1, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304_OV81Op", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0", "skips": [], "log": "agents/rocko/work/queue-304/logs/baseline-underscore.tap"}
{"label": "baseline-hyphen", "start": "2026-10-23T11:05:07.003823+00:00", "end": "2026-10-23T11:05:12.829873+00:00", "seconds": 5.826, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304-UnWJbm", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0", "skips": [], "log": "agents/rocko/work/queue-304/logs/baseline-hyphen.tap"}
{"label": "fixed-mktemp", "start": "2026-10-23T11:05:12.834143+00:00", "end": "2026-10-23T11:05:18.560124+00:00", "seconds": 5.726, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304yw6LWT", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9", "skips": [], "log": "agents/rocko/work/queue-304/logs/fixed-mktemp.tap"}
{"label": "fixed-underscore", "start": "2026-10-23T11:05:18.564553+00:00", "end": "2026-10-23T11:05:24.340312+00:00", "seconds": 5.776, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304_MyhpSs", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9", "skips": [], "log": "agents/rocko/work/queue-304/logs/fixed-underscore.tap"}
{"label": "fixed-space", "start": "2026-10-23T11:05:24.344312+00:00", "end": "2026-10-23T11:05:30.170101+00:00", "seconds": 5.826, "exit": 0, "counts": {"tests": 10, "pass": 10, "fail": 0, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304 i5d7HA", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "7690a4a945e45f5606633d36a3db9a160892be9d125d40351cd848f5bf3ea9c9", "skips": [], "log": "agents/rocko/work/queue-304/logs/fixed-space.tap"}
{"label": "M1-old-regex", "start": "2026-10-23T11:05:30.173937+00:00", "end": "2026-10-23T11:05:36.100361+00:00", "seconds": 5.926, "exit": 1, "counts": {"tests": 10, "pass": 9, "fail": 1, "cancelled": 0, "skipped": 0}, "tmpdir": "/mnt/storage/scratch/tmp/rk304_595gfj", "head": "c16e943b90ef71ac96cfa5314bbea6040634d14d", "testSha256": "3a43f134d334e985e6dd50f641a7c1788de29beb86e2f9e2ac7f0ceaf924a8e0", "skips": [], "log": "agents/rocko/work/queue-304/logs/M1-old-regex.tap"}
+66
View File
@@ -0,0 +1,66 @@
from pathlib import Path
import subprocess,os,signal,re,json,hashlib,time
from datetime import datetime,timezone
ROOT=Path(__file__).resolve().parents[4];P=Path(__file__).resolve().parent;O=P/'logs';O.mkdir(exist_ok=True)
FILE='packages/harness/tests/claude-session.test.mjs';source=ROOT/FILE
BASE='c16e943b90ef71ac96cfa5314bbea6040634d14d'
old='realpathSync(workspace).replace(/[/.]/g, "-")';new='realpathSync(workspace).replace(/[^A-Za-z0-9]/g, "-")'
original=source.read_bytes();assert original.decode().count(old)==1
fixed=original.decode().replace(old,new,1).encode()
rows=[];temps=[]
def stamp():return datetime.now(timezone.utc).isoformat()
def verify(data):
assert subprocess.check_output(['git','rev-parse','HEAD'],cwd=ROOT,text=True).strip()==BASE
assert source.read_bytes()==data
def run(label,template,data):
verify(data);tmp=subprocess.check_output(['mktemp','-d',template],text=True).strip();temps.append(tmp)
env=os.environ.copy();env.update(TMPDIR=tmp,DOCKER_HOST='unix:///mnt/storage/scratch/rocko-r304-dead.sock',NO_COLOR='1');env.pop('MOSAIC_GITEA_CREDENTIAL_FILE',None);env.pop('MOSAIC_LIVE_PROVIDER',None)
args=['timeout','-k','10','180','node','--test','--test-reporter=tap',FILE];log=O/(label+'.tap');start=stamp();begin=time.monotonic()
print('START',label,start,tmp,flush=True)
with log.open('wb') as f:
child=subprocess.Popen(args,cwd=ROOT,env=env,stdout=f,stderr=subprocess.STDOUT,start_new_session=True)
try:rc=child.wait(timeout=195)
except subprocess.TimeoutExpired:
os.killpg(child.pid,signal.SIGKILL);child.wait();rc=124
finally:
try:os.killpg(child.pid,signal.SIGTERM)
except ProcessLookupError:pass
text=log.read_text(errors='replace');counts={k:int(m[-1]) if (m:=re.findall(r'^# '+k+r' (\d+)\s*$',text,re.M)) else None for k in ['tests','pass','fail','cancelled','skipped']}
row=dict(label=label,start=start,end=stamp(),seconds=round(time.monotonic()-begin,3),exit=rc,counts=counts,tmpdir=tmp,head=BASE,testSha256=hashlib.sha256(data).hexdigest(),skips=[x for x in text.splitlines() if re.search(r'^skip\b|# SKIP\b',x,re.I)],log=str(log.relative_to(ROOT)))
rows.append(row)
with (P/'results.jsonl').open('a') as f:f.write(json.dumps(row)+'\n')
print('END',json.dumps(row),flush=True)
assert rc not in [124,137,-9] and 'SyntaxError:' not in text,'invalid run '+label
verify(data);return row,text
def red(label,template,data):
r,t=run(label,template,data)
assert r['exit']!=0 and r['counts']['tests']==10 and r['counts']['pass']==9 and r['counts']['fail']==1 and r['counts']['cancelled']==0 and r['counts']['skipped']==0,'unexpected red counts '+label
names=re.findall(r'^not ok \d+ - (.*)$',t,re.M)
assert len(names)==1 and "CLAUDE.md files and auto-memory" in names[0] and ':298:10' in t and 'ERR_ASSERTION' in t and 'MARKER-MEMORY' in t,'unexpected red '+label
def green(label,template,data):
r,t=run(label,template,data)
assert r['exit']==0 and r['counts']['tests']==10 and r['counts']['pass']==10 and r['counts']['fail']==0 and r['counts']['cancelled']==0 and r['counts']['skipped']==0,'unexpected mapping/refusal '+label
try:
red('baseline-underscore','/mnt/storage/scratch/tmp/rk304_XXXXXX',original)
green('baseline-hyphen','/mnt/storage/scratch/tmp/rk304-XXXXXX',original)
# Both installed-CLI probes now confirm the approved mapping before editing.
source.write_bytes(fixed)
green('fixed-mktemp','/mnt/storage/scratch/tmp/rk304XXXXXX',fixed)
green('fixed-underscore','/mnt/storage/scratch/tmp/rk304_XXXXXX',fixed)
green('fixed-space','/mnt/storage/scratch/tmp/rk304 XXXXXX',fixed)
source.write_bytes(original)
try:red('M1-old-regex','/mnt/storage/scratch/tmp/rk304_XXXXXX',original)
finally:source.write_bytes(fixed)
verify(fixed);print('BUILD_GREEN',stamp(),flush=True)
finally:
found=[]
for proc in Path('/proc').glob('[0-9]*'):
try:
name=(proc/'comm').read_text().strip();cwd=str((proc/'cwd').resolve())
if any(x in name for x in ['node','chrom','claude']) and any(cwd==r or cwd.startswith(r+'/') for r in [str(ROOT),*temps]):found.append(dict(pid=int(proc.name),name=name,cwd=cwd))
except (FileNotFoundError,PermissionError,ProcessLookupError):pass
(P/'cwd-sweep.json').write_text(json.dumps(found,indent=2)+'\n');print('CWD_SWEEP',found,flush=True)
@@ -12,6 +12,11 @@ export COMPOSE_PROJECT_NAME="$(basename "$(dirname "$G")")"
trap 'docker network rm "${COMPOSE_PROJECT_NAME}_default" >/dev/null 2>&1 && echo "removed network ${COMPOSE_PROJECT_NAME}_default"' EXIT
# A gate tree without the node_modules link fails cli tests with ENOENT (2026-10-13).
[ -d "$G/node_modules" ] || { echo "STOP no node_modules in $G"; exit 8; }
# Gate rule (mos-1455 item 4, 2026-10-23): every landing gate runs the bus, cli, plugins, webui and business
# suites whatever the row touches. The 270+271 gate skipped plugins, and its red sat on refactor unseen.
for p in bus cli plugins webui business; do [ -d "$G/packages/$p/tests" ] || { echo "STOP no packages/$p/tests"; exit 8; }; done
# mos-1453: a gate on the canonical node_modules must not let the webui editor rebuild test skip (Vikunja 597).
export MOSAIC_REQUIRE_EDITOR_BUILD=1
echo "base $(git rev-parse --short HEAD) dirty $(git status --short | grep -vc node_modules) DOCKER_HOST=${DOCKER_HOST:-unset}"
run() { local name="$1" dir="$2" cmd="$3"
(cd "$G/$dir" && bash -c "$cmd") > "$OUT/$name.log" 2>&1; local rc=$?
+638
View File
@@ -0,0 +1,638 @@
# PRD 1.17 draft: Case 2 story files are DOCX (Q235 to Q237, P1 to P6)
Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 8. It is d7 plus Mos's d8 fix list mos-1443 (see "d8 changes"). d7 is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
- Mos's d4 check ruling mos-1372 (D1 to D14; D15 was dropped).
- mos-1374, which replaces J-c.
- The impact map mos-1368 (jason-master-restart d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`: A1 to A16, critic items C1 to C6, items 2 to 7).
- Mos's F4 measurement.
Review order (mos-1367 B5): Webby, then Mos, then Jason. Base: PRD 1.16, `docs/prd/mosaic-stack.md` blob a6db9b4a at 482e865ac.
1.16 lands first (row 291), and this text is a new version on top of it. Nothing here is built or queued. The impact map's queue changes (A24, A57 to A59) go through `scripts/mosaic queue` after 1.17 is approved (C9).
Sources, verbatim in Mos's DECISIONS:
- Q235, Jason's Q36 answer.
- Q236: chapters are DOCX, Markdown for agent files and docs.
- Q237: "It is correct for now (subject to future change)".
- The P1 to P6 reading as restated to Jason in Q38.
## Settled points (mos-1367)
These stand as ruled. d5 changes only what mos-1372 changed.
- **O1. Where a converted chapter lives.** The DOCX lives in the writing store of
O2 design D12, and the archived Markdown stays in the user's repository under a
human-marked archive folder. Ruled yes in B1, on the condition that the text
says plainly that a converted chapter leaves the Gitea-mirrored repository and
lives in the stack's writing store. mos-1372 D7 and mos-1375 add a remote for
the store, off the stack's host.
- **O2. The unit of conversion.** One story file per import, refusing a file
that is already converted. Ruled yes in B2: each file gets its own fidelity
report, human confirmation, manifest and commit.
- **O3. Whether conversion is a vocabulary action.** A human-only verb outside
the agent vocabulary, with no role grant and no agent request path. Ruled yes
in B3.
- **O4. Reading archived Markdown.** A new `archived` scope. Lookups leave it out
unless the caller asks, every answer marks it archived, and a canon citation
can't name it. Ruled yes in B4.
## d3 choices after mos-1372
- **C1 is replaced by D1, keep-source-scope.** A DOCX keeps its source's scope:
universe, book, and canon or playground. A playground DOCX follows the
playground rules and is never returned as canon. The import accepts canon and
playground story files. Type-folder files and supporting files stay Markdown
(P4). This also settles the impact map's Q18, item 4. It leaves one narrowing
for Jason, J-i. A playground DOCX can't become canon in 1.17.
- **C2 stands.** The move keeps the file's path under the archive folder:
`book1/ch3.md` goes to `<archive>/book1/ch3.md`. An archived file's universe
and book are those of its old path.
- **C3 is replaced by D3 and D14.** The archive commit is written by a new
human-only bus step (Vikunja 571) on the C2-02 write plumbing and pushed through
C2-03b. The order is the store commit and the remote's confirmation that it
holds that commit, then the archive commit and its push, then publication to the
lookups.
- **N4 stands.** REQ-LIB-8 says "a real user's books". The PRD names no user
anywhere in Case 2.
## Text changes
### Status and sources
In the Status bullet's first line, "- Status: approved, version 1.16." becomes
"- Status: approved, version 1.17." Status gains, after the two 1.16 sentences at
the end of the bullet: "Version 1.17 records Jason's rulings Q235 to Q237 of
2026-10-23: Case 2 story files are DOCX, Markdown stays for agent files and
documentation, and a human-started import converts a Markdown story file one way.
It also adds REQ-LIB-9, the Console's DOCX editor, from Jason's rulings Q195, Q226
and Q234. It changes what Case 2 requirements allow, so Mos puts the text to Jason
as written, and it lands only after his yes (mos-1366, Vikunja 570)."
New source line, above the 1.16 one: "Version 1.17 source: Jason's Q235, answering
Q36: 'How about we see a markdown file, we convert the markdown file to docx, then
archive or mark the markdown file as converted. The new standard for files in the
creating writing use case is to use docx to handle versioning and WYSIWYG. We
eliminate the potential for confusion based on the files. Agents must understand
which files are what format and where they live. The system must allow for import
and conversion. We will not run the systems in parallel. There will be an
irreversible conversion of the files.' His Q236 says: 'For creative writing
chapters, we need to utilize docx as the standard. Markdown is acceptable for
agent files, instructions, PRDs, documentation, etc. The creative writing files
should be docx for familiarity for the user.' His Q237 says 'Q38: It is correct
for now (subject to future change)', which confirms Mos's reading P1 to P6.
REQ-LIB-9 rests on his Q195 request for 'a full WYSIWYG editor within Mosaic
Stack', his Q226 answer 'F41: We want to move editing into the WYSIWYG in the
WebUI. All changes are instantly committed and available to the agents.' and his
Q234 answer 'Q37: yes' to the exact security header for the editor's frame. The
storage follows the O2 design D10 to D12 as rewritten under Vikunja 571, at
b99ca86b9 on `refactor`."
### Creative library, section lead
Replace "The library is the user's own writing repository." with "The library is
the user's own writing repository plus the stack's writing store, which holds the
DOCX story files (REQ-LIB-8, version 1.17)."
### REQ-LIB-1
Add, before "(Design 1.1": "The mapping also names one archive folder, set by the
human, where converted Markdown story files go. A book's DOCX story files are in
the stack's writing store, under the same universe and book (REQ-LIB-8)." Version
tag, inside the closing parenthesis: "Version 1.15: supporting folders.)" becomes
"Version 1.15: supporting folders. Version 1.17: the archive folder and the
writing store.)"
### REQ-LIB-2
Replace "A file's scope comes from its path alone, prefixes first: a Markdown file
under a supporting prefix is supporting, one under a playground prefix at any level
is playground, any other Markdown file in a book folder is canon, and everything
else is unmapped and never returned. A folder in both lists is supporting." with:
"A Markdown file's scope comes from its path and from past imports, prefixes
first: a Markdown file under the archive folder is archived, one under a
supporting prefix is supporting, a Markdown file at a converted file's old path is
unmapped under REQ-LIB-8 but flagged in the format map, one under a playground
prefix at any level is playground, any other Markdown file in a book folder is
canon, and everything else is unmapped and never returned. A folder in both lists
is supporting. The archive folder follows the same placement rules and may not
overlap another prefix or a book folder. An archived file keeps the path it had
under the archive folder, and its universe and book are those of that old path. A
DOCX document in the writing store keeps its source's scope: the universe, the
book if there is one, and canon or playground. Its source is the Markdown file it
was converted from or, for one the human starts in the editor, the universe and
book or the playground the human picks (REQ-LIB-8). A playground DOCX follows the
playground rules and is never returned as canon. It stays playground after its
draft universe is made real. In version 1.17 no promotion or mapping change makes
a playground DOCX canon. A later version of this PRD says how one becomes canon."
Replace "Lookups return canon unless the caller asks for playground or
supporting." with "Lookups return canon unless the caller asks for playground,
supporting or archived. Every answer marks an archived file as archived and names
the DOCX that replaced it."
Replace "so an agent can't cite a playground or supporting file as canon" with "so
an agent can't cite a playground, supporting or archived file as canon".
In REQ-LIB-2 only, replace "The Console shows each cite's scope." with "The Console shows each cite's
scope and format, and whether a cited Markdown file was converted."
Version tag, inside the closing parenthesis: "Version 1.15: supporting.)" becomes
"Version 1.15: supporting. Version 1.17: archived scope and DOCX scope.)"
### REQ-LIB-4
Replace "The user commits and pushes with their own tools, today Obsidian with
obsidian-git." with "The user commits and pushes Markdown with their own tools,
today Obsidian with obsidian-git. A converted story file's current text is no
longer in that repository, which keeps only its archived Markdown (REQ-LIB-8)."
Add, after "Bus writes and pushes come in later rows (C2-02, C2-03b).": "A DOCX
story file is saved in the Console's editor, and each accepted save is one commit
by the human in the stack's writing store (REQ-LIB-9). Each writing-store commit
is also pushed to a remote off the stack's host, never by force-push. The remote
is a setting only a human changes, and it starts unset. A repository on the
stack's host never counts as that remote. A missing remote or a failed push never
undoes a save, and the user sees each push that is pending or failed. Only the
stack's host pushes to that remote, and no agent can reach it with any credential.
Agents read the local commit."
Replace "Agents read only the mirror's served commit and never hold a copy of
their own, so they see the user's push within about a minute." with "Agents read
the user's repository only at the mirror's served commit, except a story file
during its import under REQ-LIB-8. They read a DOCX story file only at a committed
version in the writing store, under REQ-LIB-5. They never hold a copy of their
own, so they see the user's Markdown push within about a minute and a DOCX save at
their next lookup after the editor reports it as committed."
Version tag: "and the sync items.)" becomes "and the sync items. Version 1.17,
with Q222 for the remote setting.)"
### REQ-LIB-5
Add, after "Each answer names the commit it read.": "Each answer also names the
file's format and where it lives: its path, or its document id in the writing
store. A DOCX story file is read from its latest committed save, never from
unsaved text. A lookup that starts after the editor reports a save as committed
returns that save or a later one, even while its push to the remote is pending or
has failed, and the answer names the version. Get, search and manuscript read a
DOCX story file as its extracted text with DOCX locators, never as lines. That
text is the file with its tracked changes accepted. An agent reads a story file's
comments, or its text with tracked deletions shown, only after a human turns on
the matching setting. Both settings are off by default, and only a human changes
them. The reader in this version can't read some things a DOCX story file can
hold, such as a web link, a text box, a field or hidden text. The editor warns the
human when an edit adds one, and the human can still save it. Until a later save
takes it out, lookups give agents none of the text of the part that holds it,
whether that is the chapter's body, a note or a comment. The manuscript lookup
follows the book's chapter order across both formats (O2 design D10), and the next
chapter it names is never an archived file. Links out and in resolve across both
formats. The universe lookup returns the format map (REQ-LIB-8)."
Replace "The user sees them in Obsidian's graph and backlinks." with "The user sees
a Markdown file's links in Obsidian's graph and backlinks. A DOCX story file's
wikilinks stay clickable in the Console's editor (O2 design D11), and no graph
shows them yet."
Version tag: 'and "Connections are always visible".)' becomes 'and "Connections
are always visible". Version 1.17, with Q222 for the two settings.)'
### REQ-LIB-6
Replace "A reply may cite lines of the repository at a commit or pages the agent
fetched, and the broker checks every citation before it stores the message." with
"A reply may cite lines of a Markdown file at a commit, a place in a committed
DOCX story file, or pages the agent fetched, and the broker checks every citation
before it stores the message. A DOCX citation never calls a paragraph a line. It
names the document id, the committed version and a DOCX locator. A citation that
names an archived file as current is refused. The `requireCitation` setting below
applies to Markdown and DOCX citations alike, and so does the rule for a citation
marked as canon."
Add, after "the broker refuses such a write before any git call.": "The writing
store and the archive folder are read-only to a creative agent as well, and the
broker's path check treats the archive folder as story. No agent creates, edits,
deletes, converts, archives or restores a story file in any format, and no agent
writes a DOCX. No creative agent starts or undoes a conversion, and none asks for
one through a tool or a decision it raises, as REQ-LIB-8 says. An agent may still
suggest a conversion to the human in chat. Each creative role's contract states
which files are DOCX and which are Markdown, and cites a DOCX story file by its
locator, never by line."
Version tag, inside the closing parenthesis: "Version 1.15.)" becomes "Version
1.15. Version 1.17.)"
### New REQ-LIB-8
This bullet goes after REQ-LIB-7 and before the "### Installer" heading.
"- **REQ-LIB-8.** Story files become DOCX. DOCX is the standard for chapters and
the other story text in a Case 2 writing project, canon and playground alike. A
story file becomes DOCX through a human's import (below), or when the human
starts it in the editor. From then on DOCX is its only working format, edited in
the Console's editor (REQ-LIB-9). A Markdown story file stays Markdown until its
import. Files in a type folder (REQ-LIB-1), such as character and place notes,
stay Markdown. So do supporting files, agent files, instructions, PRDs and
documentation, including the character sheets, summaries and cross-links agents
keep under REQ-LIB-6.
- A converted story file's current text leaves the user's Gitea-mirrored
repository. Its DOCX lives in the stack's writing store, on the stack's host,
and not in the repository the user pushes or opens in Obsidian. Only its
archived Markdown stays there.
- An import converts one Markdown story file, canon or playground, outside every
type folder (REQ-LIB-1), to a DOCX document in the writing store. It refuses
an unmapped, supporting, archived or type-folder file, and a file that is
already converted. Each import has its own fidelity report, the human's
confirmation of that report, a manifest naming the source file and its commit,
and one commit in the store. The DOCX keeps every wiki link of the source as
written, and a lost link to another library file fails the fidelity report. A
web link becomes plain text that keeps its label and address, and the fidelity
report lists it for the human to confirm. A web link that can't be kept safely
as plain text refuses the import.
- The human can also start a new story file in the editor, in a universe and
book or a playground the human picks. It is a DOCX document in the writing
store from its first commit, with no Markdown copy, and it opens under the
same gate as the import.
- The same import marks the Markdown file converted, naming the document, and
moves it to the archive folder under its old path (REQ-LIB-1, REQ-LIB-2).
The mark and the move are one commit authored by the human in the user's
repository. A new human-only bus step (Vikunja 571) on the C2-02 write
plumbing writes it, and C2-03b pushes it. The move keeps the bytes and the
git history, so it isn't `data.delete`.
- The store commit comes first, and the writing store's remote must confirm it
holds that commit. Then come the archive commit and its push, then publication
to the lookups. Until publication the Markdown at its old path keeps its canon
or playground scope. Agents read that file at the commit the import started
from, even after the mirror's served commit reaches the archive commit or a
later one. No lookup returns the file's archived copy before publication, even
one that asks for archived files. A refusal or failure before the archive push
leaves the Markdown unmoved and no DOCX that a lookup returns. A failure after
the archive push leaves a pending import that the human sees and can resume,
never a claim that nothing moved. An archive push whose result is unknown
counts as a failure after the archive push until the stack reads the result
back from the user's remote.
- Each stage of an import writes a logged event. The import's record on the
stack's host names the source file's path, commit and blob and the new
document id, and each event points to that record.
- After a conversion, a Markdown file at the converted file's old path is neither
canon nor playground. Lookups leave it out, the format map flags it, and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
canon until its import, unless it sits at a converted file's old path or under
a playground prefix. The Console flags it and offers the import. Nothing
converts it on its own.
- A hybrid's exclusion that covers a converted file's old path also covers its
DOCX document (REQ-LIB-3). An exclusion of a whole book folder also covers
every DOCX document in that book, including one started in the editor. An
exclusion can also name a DOCX document by its id.
- Only a human starts an import or a new story file, from the CLI or a Console
session signed in as that human. Both are human verbs outside the agent action
vocabulary. Neither is added to `ACTIONS` in the business vocabulary or to the
broker's always-gated list, including the import's action, `library.convert`.
No role holds them, no agent can request them, and the hard-wait list in
REQ-WAIT-1 doesn't change. In an organization with several humans, who may
start them is a setting only a human changes, and the default is the owner
only.
- Conversion is one way, with no parallel run. Neither the Console nor any agent
edits a converted story file as Markdown, and no DOCX edit syncs back to
Markdown. The Console's Markdown save refuses every file under the archive
folder. The user's own tools can still change the archived Markdown, and no
such change reaches the DOCX. The old Markdown stays in git history, and
nothing rewrites history. A promotion into a book folder (C2-19) runs the
import for a Markdown story file or is refused. A promotion of a playground
DOCX is refused in version 1.17.
- Agents get a format map: for each mapped folder and story file, its format,
scope and where it lives, archived files, flagged files and pending imports
included.
- The import and new story files ship closed. They open only after four things.
The editor ships. The human has set the writing store's remote, and the stack
has checked that it can push there. Agents can read committed DOCX story files
through the lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent
system that writes the user's files has stopped, which for Case 2 is the
Hermes retirement (#423 step 5). So the import itself never cuts agents off
from a chapter, the chapter's current text never lives only on the stack's
host, and no second system writes the story. The first import of a real user's
books waits for Jason's go.
(Q235, Q236, Q237: P1 to P6. Q222 for the setting. O2 design D10 to D12.
Version 1.17.)"
### New REQ-LIB-9
This bullet goes after REQ-LIB-8.
"- **REQ-LIB-9.** The Console's DOCX editor. The human edits a DOCX story file in
the Console's editor, in a split pane with the agent chat alongside it. The
human can pin an agent chat to a document. The human can select a word,
right-click it, and choose a synonym from a thesaurus. Only a human saves. Saves
happen on their own while the human types (Q226), at intervals only a human
changes, and each accepted save is one commit with the human as author, pushed
as REQ-LIB-4 says. A save made against an older version than the current one is
refused. Agents never write a DOCX. Versioning is the editor's tracked changes
plus one git commit per save. The editor's code is vendored and pinned, with a
hash per file and the licenses that its independent security review accepted. At
runtime the editor loads only files that the stack itself serves, and it
requests nothing from any other host. The editor's frame sends exactly the
content security policy Jason approved in Q234, and the WebUI shell's own header
is unchanged. REQ-PROJ-3's project docs editor is unchanged. (Q195, Q226, Q234,
Q236. O2 design D3, D4, D6, D8, D10, D13, D14, D16 and D22. Version 1.17.)"
### Version 1.17 notes
Added after the 1.16 notes:
"- Version 1.17:
- Jason confirmed the reading 'for now (subject to future change)' (Q237). The
rulings stand until he revises them.
- The 10-27 Case 2 release ships on Markdown as built (P6). The 1.11 line 'The
Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG editor (Q195,
Q196) is an add-on that doesn't gate it.' still holds. Conversion opens after
10-27, and only once REQ-LIB-8's four conditions are met. The editor ships,
the writing store's remote is set and the stack has checked that it can push
there, agents can read and cite committed DOCX story files through the
lookups, and Hermes is retired, which is #423 step 5.
- The 1.12 note says five bus write and Console editor rows, C2-02, C2-03b,
C2-11a, C2-11b and C2-19, don't gate 10-27. Jason's Q226 F42 later moved
C2-02, C2-03b and C2-11b into the 10-27 slice. The Case 2 design brings C2-11a
in with them, because C2-11b needs it. C2-19 stays outside the slice. Mos's
reading of F42 is that C2-02 and C2-03b now gate 10-27, because design 9.2
puts them in 'the smallest slice that lets her start'. C2-11a and C2-11b are
slice rows that still don't gate 10-27, because the editor is the Q195 add-on.
The DOCX change in 1.17 leaves this slice as it is, and C2-11b's Markdown
editor never edits a converted story file.
- Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9). The
1.12 note that the Case 2 design keeps Obsidian now holds only for Markdown
files, including story files not yet imported. A DOCX story file is edited
only in the Console's editor under REQ-LIB-9.
- This replaces two parts of the O2 design at 25c09f19f. The superseded-notice
proposal from Fenton's F1 in D12 is withdrawn, and the
keep-Markdown-at-its-path model of D11 is replaced. Gyro's rewrite of the O2
design for Vikunja 571, landed on `refactor` at b99ca86b9, carries the
details, and where it differs from this text, this text holds:
- the import and archive steps, the mark, and the new bus step;
- links from supporting files to a moved chapter;
- the DOCX reader;
- the user's unpushed or later changes to an archived file or at its old path,
and pushing and closing the file in Obsidian before each import;
- the writing store's remote in W4 section 7.3, which replaces the no-remote
and no-backup rule that W4 section 7.4 held before the rewrite, at
062646487;
- import events counted by a bus restore, and recovery when a bus restore or a
restore of the whole host predates an import;
- manifest order for the manuscript lookup and its next chapter;
- `library.convert` kept out of `ACTIONS`, out of the hard-wait note and out
of the pinned `GATED_ONLY` list in packages/business/src/vocabulary.mjs.
- Agents read a converted story file through the Case 2 lookups under the same
project and universe access as Markdown canon, in any thread or room. W4's
earlier O2-only conversations, per-document grants and per-send sharing don't
gate these lookups. The 571 rewrite replaced them in W4 sections 7.1 and 7.2.
The creative tool list in REQ-LIB-6 doesn't change.
- The six creative role contracts row 213 landed say 'Every item cites the
lines of the user's material it is about'. A build row under 571 changes that
line for DOCX story files (REQ-LIB-6).
- A new Markdown file in a book folder is still canon in 1.17. The 1.17
exception is a file at a converted file's old path, which REQ-LIB-2 makes
unmapped.
- Story files convert whether canon or playground. Type-folder and supporting
files stay Markdown.
- Measured 2026-10-23 at about 01:21Z: the Case 2 user's library repository
tracks 213 files, 203 of them Markdown, and none is a .docx or .doc. It has no
untracked files. The user's .doc files (Q196) are outside it. So the landed
code has no DOCX there to report as unreadable at 10-27.
- Text replaced in 1.17, kept here as in earlier notes:
- Creative library lead, the 1.16 sentence: "The library is the user's own
writing repository."
- REQ-LIB-2, the 1.16 sentences: "A file's scope comes from its path alone,
prefixes first: a Markdown file under a supporting prefix is supporting,
one under a playground prefix at any level is playground, any other
Markdown file in a book folder is canon, and everything else is unmapped
and never returned. A folder in both lists is supporting." "Lookups return
canon unless the caller asks for playground or supporting." "so an agent
can't cite a playground or supporting file as canon" "The Console shows
each cite's scope."
- REQ-LIB-4, the 1.16 sentences: "The user commits and pushes with their own
tools, today Obsidian with obsidian-git." "Agents read only the mirror's
served commit and never hold a copy of their own, so they see the user's
push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
stood at conversion, until a Console view of their links exists. Q226
records that view as a later option.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
- REQ-LIB-1 only gains sentences.
- REQ-LIB-3's text and REQ-LIB-7 are unchanged. REQ-LIB-8 adds one form of
exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. A path
exclusion also covers DOCX documents, through a converted file's old path or a
whole book folder. An archived file and a DOCX document keep a universe, and a
book where their source has one, so every lookup still names one universe.
REQ-PROJ-3, the Console editor for a project's spec and docs, is unchanged.
C2-11b opens the library's Markdown files in that same editor.
- In `packages/tasks/requirements.json` the titles of REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 change, and those of REQ-LIB-5 and REQ-LIB-6 stay. REQ-LIB-1 becomes
'A human-set library maps the user's repository onto universes, books, types,
playgrounds, supporting and archive folders'. REQ-LIB-2 becomes 'Scope comes
from the path or a DOCX's source, lookups default to canon, and only canon
files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change. The
bus fast-forwards a read-only mirror every 60 seconds, the one place agents
read Markdown'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes from 85
to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only
one-way import or in the editor; Markdown stays for notes and docs', and
REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one
commit, and the editor code is vendored and pinned'.
- Open points O1 to O4 of draft 1 were ruled by Mos in mos-1367 B1 to B4.
- Sage files the build rows under Vikunja 570 and 571 after the rewrite is
approved. This amendment adds the requirements only."
### Risks
The new lines go at the end of "## Risks and open questions", after the bullet
that begins "Trusted mode lets an agent" and before the "## Milestones" heading.
- "A DOCX file is a zip archive, so git can't show a line diff of a chapter.
Review of a story change goes through the editor and the committed reader,
not `git diff`. (Version 1.17.)"
- "A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
chapter only through its archived Markdown, and no DOCX edit reaches that file's
links. The DOCX's current links show only in the Console's editor until a
Console view of them exists. (Version 1.17.)"
- "Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
confirmation, an untouched original on refusal, and the kept archive.
(Version 1.17.)"
- "A restore of the stack's bus alone from a backup taken before an import drops
the import's logged events, while the archive commit and the DOCX stay. A
restore of the stack's whole host from such a backup also takes the writing
store and the host's import records back to before the import. The archive
commit stays in the user's Gitea repository and the DOCX stays on the writing
store's remote, but the host keeps no record of the import. A bus restore counts
import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)"
## d8 changes (Mos's d8 fix list, check wf_63bba879-180)
- R1: REQ-LIB-4's tail covers DOCX saves, which agents see at their next lookup after the editor reports them as committed. The notes quote the whole 1.16 sentence. Source: nit/jason/F10.
- R2: REQ-LIB-4's new title says the mirror is "the one place agents read Markdown". Source: decisions.n14.
- R3: J-b asks whether 1.17 requires the Console Connections view, with Mos's recommendation of yes. Sources: mos1372/F9, jason/F2, anchors/F5.
- R4: C2-02 and C2-03b gate 10-27, C2-11a and C2-11b are slice rows that don't, and "F42 wins" is gone. New J-k. Sources: anchors/F6, jason/F5.
- R5: where the landed 571 design differs, this text holds. Sources: anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6, G16's design follow-on.
- R6: `library.convert` stays out of `ACTIONS`, and REQ-WAIT-1's hard-wait list doesn't change. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
- R7: a repository on the stack's host never counts as the writing store's remote, and Item 2 says so. Sources: mos1372/F2, adversarial/F1, gyro571/F2.
- R8: "no agent credential reaches it" becomes "no agent can reach it with any credential". Source: Fenton's B1 under mos-1379.
- R9: no playground DOCX becomes canon in 1.17, and a promotion of one is refused. New J-i. Sources: mos1372/F8, adversarial/F2.
- R10: REQ-LIB-9 restates Q195's pin and right-click thesaurus, and its cite gains D4 and D22. Source: critic/C4.
- R11: REQ-LIB-9's license bar is the licenses its independent security review accepted. New J-l. Source: critic/C3.
- R12: no text for D16's narrow-window tabs. Mos records that reading in STATE. Sources: jason/F1, second verifier vote.
- R13: no lookup returns a file's archived copy before publication. Source: adversarial/F5 part 3.
- R14: a restore of the whole host from a backup taken before an import is a fourth recovery case. Source: adversarial/F6.
- M1: REQ-LIB-4's writing-store sentences follow the later bus rows sentence and add the off-host remote rules and DOCX read timing. The note quotes both 1.16 sentences. Sources: mos1372/F2, adversarial/F1, gyro571/F2, anchors/F4, nit/jason/F10, adversarial/F5 part 1, Fenton B1.
- M2: REQ-LIB-8's order bullet adds the remote's confirmation, the old-path read rule and "archive push". Sources: mos1372/F2 and the other remote findings, mos1372/F7, adversarial/F5 parts 2 to 4.
- M3: REQ-LIB-8's gate bullet has four conditions, adding the remote. Sources: the remote findings, critic/C1 and C2 edit 2.
- M4: the 1.17 note on when conversion opens restates the four conditions and drops W4b. Sources: jason/F5, the remote findings.
- M5: Item 2 rewritten with the remote rules. Sources: the remote findings.
- M6: the 1.12 gating note rewritten. Sources: anchors/F6, jason/F5.
- M7: REQ-LIB-9 gains the split pane, the pin, the thesaurus, the license bar and the runtime load rule, and cites more of the design. Sources: jason/F1, critic/C4, critic/C3, nit/anchors/F12, nit/gyro571/F9.
- M8: REQ-LIB-2, the one-way bullet and the C1 note keep a playground DOCX out of canon in 1.17. Sources: mos1372/F8, adversarial/F2.
- M9: REQ-LIB-5 says what the reader can't read, and the import turns web links into plain text. New J-j. Sources: critic/C1, critic/C2.
- M10: an exclusion of a whole book folder covers that book's DOCX documents. Source: adversarial/F3, with B5.
- M11: the human-verbs sentence and the 571 list item name `library.convert` and REQ-WAIT-1. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
- M12: J-b rewritten. Sources: mos1372/F9, jason/F2, anchors/F5, gyro571/F3, critic/C6.
- M13: J-d puts the agent read model to Jason as his call. Source: mos1372/F1.
- M14: Status moves to version 1.17 and names REQ-LIB-9. Sources: anchors/F2, mos1372/F4, critic/C7.
- M15: the 1.17 source line quotes Q235 in full (mos-1446) and the REQ-LIB-9 sources, at b99ca86b9. Sources: critic/C5, anchors/F9, jason/F8 pin part, nit/gyro571/F8, critic/C6 pin part.
- M16: the 571 list lead names the two replaced design parts and the landed design. Sources: anchors/F9, anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6.
- Landing swap: Gyro's docs round for R5, R6 and R14 landed as b99ca86b9 (Fenton fenton-0015), so M15 and M16 cite b99ca86b9 in place of 767bb62c7, as the d8 fix list's header says. J-b keeps 767bb62c7 for its line numbers.
- M17: W4 section numbers in the remote item and the lookups note. Sources: gyro571/F5, nit/adversarial/F11.
- M18: the D7 landing line. Sources: gyro571/F5, the remote findings.
- M19: a logged event for each import stage, in REQ-LIB-8 and A2. Source: gyro571/F7.
- M20: the Risks line on restores adds the whole-host case. Sources: adversarial/F6, gyro571/F7.
- M21: the 571 restore item adds the whole-host case. Source: adversarial/F6.
- M22: the graph Risks line and the REQ-LIB-5 note name a Console view. Sources: anchors/F5, jason/F2.
- M23: the d6 B3 line drops "F42 wins". Source: anchors/F6.
- M24: new Markdown in a converted book, with the old-path and playground exceptions. Source: mos1372/F6.
- M25: J-c's last sentence is Mos's reading. Source: mos1372/F10.
- M26: J-h rewritten with the D13 defaults. Source: jason/F3.
- M27: Item 4 rewritten. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
- M28: the "Not taken" note on unmarked folders. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
- M29: the import refuses an unmapped file. Sources: gyro571/F4, decisions.unmarked_folders.
- M30: placement notes for REQ-LIB-8, REQ-LIB-9 and the Risks lines. Sources: anchors/F3, nit/anchors/F14.
- M31: REQ-LIB-4's title and the D11 landing line. Source: decisions.n14.
- M32: the Q236 note limits the 1.12 Obsidian note to Markdown files. Source: critic/C8.
- M33: the exclusion note adds path exclusions of DOCX documents. Source: anchors/F7.
- M34: A9 moves from "Mine" to the impact-map list. Source: decisions.exclusion_by_id.
- M35: the D5 landing line. Sources: anchors/F10, mos1372/F5, nit/adversarial/F12.
- M36: the D9 and D14 landing lines. Source: mos1372/F1.
- M37: the O1 and C3 notes add the off-host remote and its confirmation. Sources: the remote findings.
- M38: the REQ-PROJ-3 note. Source: nit/anchors/F11.
- M39: REQ-LIB-5's freshness sentence keys on the editor reporting a save as committed. Source: nit/adversarial/F9.
- M40: REQ-LIB-6 bars agent requests for a conversion and allows a suggestion in chat. Source: nit/jason/F9.
- M41: REQ-LIB-2's opening and old-path rule, and the "Mine" note. Source: nit/adversarial/F10.
- M42: REQ-LIB-8's lead sentence on Markdown story files. Source: nit/mos1372/F12.
- M43: REQ-LIB-6's citation sentence. Source: nit/anchors/F13.
- M44: REQ-LIB-5's version tag uses plain quotes. Source: nit/anchors/F15.
- M45: reference codes B5, D12, D10 and B4 name their source. Source: nit/mos1372/F11.
- Files the row would own: four files, with the id count going to 87. Sources: anchors/F1, adversarial/F4, decisions.n14 F4.
## d7 changes (mos-1379, Fenton's 571 B2)
- REQ-LIB-5 gains Mos's three sentences after the extracted-text sentence. The text agents read has tracked changes accepted. Comments, and text with tracked deletions shown, each wait for a setting only a human turns on, off by default. Its version tag cites Q222 for the two settings, which is the only text here beyond Mos's.
- J-d gains Mos's sentence on comments and tracked deletions.
- Fenton's B1 is design only. d6's "no agent credential reaches it" holds, and the shared helper stays #431.
## d6 changes (Webby round 3, webby-0207)
- B1: titles for REQ-LIB-8 and REQ-LIB-9 in the requirements.json note and the file list. REQ-LIB-9 takes Webby's title. REQ-LIB-8 keeps mine, because Webby's ("...one-way import that archives the Markdown under its old path") leaves out D2's story files started in the editor.
- B2: REQ-LIB-2 makes a Markdown file at a converted file's old path unmapped, ahead of the playground and canon rules, so a playground conversion is covered too. REQ-LIB-8's old-path bullet says "neither canon nor playground".
- B3: the slice note names Q226 F42's three rows (C2-02, C2-03b, C2-11b). C2-11a joined the slice with C2-11b, because C2-11b needs it. Both are slice rows that don't gate 10-27, as design 9.2, case2-rows.md:1655 and the impact map's A57 say. The 'F42 wins' sentence is gone from the note in d8, so no queue change follows from it.
- N10: the cite-scope replacement is scoped to REQ-LIB-2. The 1.14 note's copy stays.
- N11: the notes say REQ-LIB-8 adds the by-id exclusion form REQ-LIB-3 doesn't have.
- N12: J-h marks the autosave intervals for Jason.
- N13: J-b names REQ-LIB-5's "Connections are always visible" tag and Q226's "another way to handle the visual brain map".
- N14, not taken: REQ-LIB-4's title stays internal F3's text, as mos-1372 D11 ruled. Webby's alternative, "...and agents read Markdown from it alone" (120 chars), is Mos's call.
## Where each mos-1372 item landed
- D1: REQ-LIB-2's DOCX sentences, the REQ-LIB-8 lead and import bullet, C1 above, and the notes. "Playground files stay Markdown in 1.17" is gone.
- D2: REQ-LIB-2's "for one the human starts in the editor" and the REQ-LIB-8 new-story-file bullet. The universe-and-book note is now true.
- D3: the REQ-LIB-8 order bullet. This also settles N8.
- D4: REQ-LIB-8's one-way bullet, which carries the archive refusal and consequence F2's text, and the 571 list item.
- D5: it landed in REQ-LIB-2's "a Markdown file at a converted file's old path is unmapped" in Webby's B2 form, and in the REQ-LIB-8 old-path bullet.
- D6: REQ-LIB-5's freshness sentences.
- D7 and mos-1375: REQ-LIB-4's remote sentences, the remote step in REQ-LIB-8's order bullet, the fourth opening condition and the 1.17 note on when conversion opens, plus the 571 list item on W4 section 7.3, which replaces the no-remote rule of the old W4 section 7.4. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it".
- D8: the REQ-LIB-5 replacement, the import bullet's wiki-link sentence, the Risks line, the note quoting the old sentence, and "REQ-LIB-1 only gains sentences".
- D9: the 1.17 note that agents read a converted story file through the Case 2 lookups, from the d4 check's consistency B2, and J-d, which puts the read model to Jason as his call.
- D10: the d4 check's B5 sentence, in REQ-LIB-8.
- D11: internal F3's clause in the REQ-LIB-4 section, and in the notes the title as Mos amended it for N14.
- D12: the whole 1.16 sentence replaced, and quoted in full in the notes.
- D13: consequence F1's text, in REQ-LIB-4 and REQ-LIB-8's first bullet.
- D14: the row text from the d4 check's consistency B1, in the mark-and-move bullet and C3 above.
## What d5 adds beyond mos-1372 (for Webby and Mos to check)
From the impact map, folded as mos-1368 item 1 asked:
- A1: the Console shows a cite's format and converted state (REQ-LIB-2).
- A2: the logged events and the several-humans setting, default owner only, under Q222.
- A3: the import is outside the vocabulary, the hard-wait list is unchanged, and the archive move isn't `data.delete`. The C6 code site is in the 571 list.
- A4: the section lead and REQ-LIB-1's writing-store sentence.
- A6: format and location in every answer, extracted text with DOCX locators, links across formats.
- A7: read-only extends to the store and the archive, no agent action on a story file in any format, no agent DOCX write, and role contracts state the formats. C1's role contract line is a 571 build item.
- A8: the DOCX cite shape, and a cite naming an archived file as current is refused.
- A9: a hybrid sees a source's DOCX canon through REQ-LIB-2's source scope, and REQ-LIB-8 lets an exclusion name a DOCX document by its id. The id form lets the human exclude a single DOCX started in the editor, which has no old path. An archived file isn't canon, so no hybrid sees it.
- A10: REQ-LIB-9, so the count is 87, not 86.
- A11: the Hermes gate, from item 3.
- A12: the C2-19 promotion sentence.
- A13: the broker's path check treats the archive folder as story.
- A14 and C5: two Risks lines.
- A15: the flagged-new-file bullet. It sits beside the note from the d4 check's B4, "a new Markdown file in a book folder is still canon". The file stays canon and is offered for import.
- A16: three titles change and two stay.
- C2: manifest order for the manuscript lookup.
- F3: the C2-02 gating note.
- F4: Mos's measurement.
Mine, beyond either source:
- REQ-LIB-2 opens "A Markdown file's scope comes from its path and from past imports". DOCX scope comes from the source, so "a file's" would be false.
- REQ-LIB-9's autosave sentence. It is Mos's reading of F41's "instantly committed", with human-set intervals. The exact intervals stay in the design.
Not taken:
- Impact map item 4's "files in unmarked folders are eligible one at a time", for folders outside every book folder and playground only. Their files are unmapped under REQ-LIB-2, so a DOCX made from one would have no universe or book to keep. The human maps such a folder into a book folder or a playground first, under REQ-LIB-1. A file in an unmarked folder inside a book folder or a playground is canon or playground, and the import takes it one file at a time.
- A6's named chapter-links view. D8 takes B8's "no graph shows them yet", and J-b puts the view to Jason.
## Marked for Jason (additions in the text he sees)
- J-a. Whether "We will not run the systems in parallel" applies per chapter or as one switch per book. His call. As written, it applies per chapter.
- J-b. A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the chapter only through its archived Markdown, and no DOCX edit reaches that file's links. REQ-LIB-5 still cites two TARGET lines, "Visualization of data via Obsidian or equivalent is required" and "Connections are always visible". Mos's reading is that 1.17 as written meets neither line for converted chapters. Their current links show to the user only as clickable text inside the editor, and no graph or list shows them. Q226 leaves room for "another way to handle the visual brain map". The O2 design that landed for Vikunja 571 at 767bb62c7 holds a Console Connections view and its tests as later work that waits for this answer. His call whether 1.17 requires that view. As written, it does not. Mos recommends that it does, because impact map A6 says "Recommended: name the view" and TARGET says "Connections are always visible".
- J-c (mos-1374): "The store pushes to a remote (D7), and the Case 2 VM is in the nightly PBS backup. For a fixed restore point before her first real import, a manual backup of VM 1012 with PBS protect runs first. That step is reversible and needs no reboot." Mos's reading is that this sits inside the P5 go step, so it needs no new question.
- J-d. How agents read a converted chapter, D9. His call. As written, an agent reads it through the ordinary Case 2 lookups in any thread or room, under the same project and universe access as Markdown canon. This is Mos's reading of F41 and REQ-LIB-7. The other choice is the model in the earlier O2 design, W4 sections 7.1 and 7.2. Under it, an agent reads a converted chapter only in a new writing conversation that holds one human and one agent, only after the business owner grants that agent access to that document, and by default only what the human attaches to each message. Comments and tracked deletions stay hidden from agents until a human turns on a setting for each. Markdown has neither, so the default keeps agents at the access Markdown gives them. Those two sentences are Mos's ruling mos-1379. A yes to the text as written settles it.
- J-e. Versioning means the editor's tracked changes plus one git commit per save.
- J-f. A converted chapter can be edited only in the Console while the stack is reachable. Obsidian no longer edits it offline.
- J-g. The import takes Markdown only. Importing .doc files stays a separate task.
- J-h. Autosave under REQ-LIB-9. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." Mos's reading, recorded with that ruling, is "Each save is a commit at once, and agents read it at their next lookup." At the O2 design's D13 defaults, a save commits at most 2 seconds after a pause in typing or 15 seconds into steady typing, with no human action needed. Mos's reading in mos-1359 counts that delay as "instantly committed". Only a human changes the intervals. The PRD names no values, and the defaults stay in the design. Whether a delay of seconds fits his word "instantly" is his call, so this is marked for him.
- J-i. In 1.17 a playground story that is DOCX can't become canon. That covers a playground Markdown file the human imports, a story the human starts in a playground in the editor, and the DOCX of a draft universe after it is made real. Today Melanie promotes a playground file by moving it into a book folder in Obsidian, and a DOCX in the writing store can't be moved that way. She can start a new story in the book and paste the text in, but it gets a new document id and the playground copy keeps the history. A later version adds DOCX promotion with C2-19, which comes after the slice.
- J-j. Web links, text boxes and fields in a converted chapter. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." The reader in this version can't read a web link, a text box, a field or hidden text. As written, the import turns each web link into plain text that keeps its label and address, and the human confirms that in the fidelity report. A web link that can't be kept safely as plain text refuses the import. If a later edit adds one of these, the save keeps it and the editor warns the human. Agents then get none of that part's text until a save takes it out.
- J-k. The 1.12 note Jason approved says bus writes, C2-02 and C2-03b, don't gate 10-27. His Q226 "F42: ok" moved them into the 10-27 slice, and design 9.2 counts them in the smallest slice that lets Melanie start. Mos reads that as making them gate 10-27. The editor rows C2-11a and C2-11b still don't gate it, per his Q195 words "we need to get it moving as an add-on".
- J-l. REQ-LIB-9 does not use REQ-PROJ-3's "permissive license" bar. It takes the licenses that the editor's independent security review accepted, because that review cleared fonts under the SIL Open Font License and the Ubuntu Font License, which not every reader counts as permissive. Mos's reading.
Marked as changes beyond P1 to P6, from the impact map (Q17, item 5):
- A1 (archived scope and canon cites)
- A2 (the conversion requirement, REQ-LIB-8)
- A3 (outside the vocabulary, hard-wait list unchanged)
- A6 (lookups across formats)
- A7 (agent bounds)
- A8 (DOCX cites)
- Item 2 and mos-1375: each writing-store commit is pushed to a remote off the stack's host. Only a human sets it, it starts unset, and a repository on the stack's host doesn't count. Only the stack's host pushes to it, and no agent can reach it with any credential. Import and new story files stay closed until the remote is set and checked, and each import's archive push waits until the remote holds the DOCX commit. This keeps "Git is used for all changes -- local and remote" true for DOCX chapters.
- Item 3 (the Hermes gate)
- Item 4. Canon and playground story files convert one file at a time, including files in unmarked folders inside a book folder or a playground. Type-folder and supporting files stay Markdown. A story file outside every book folder and playground has no universe or book, so it converts only after a human maps its folder into a book folder or a playground. The mapping step is Mos's reading of P1 and Q30.
The F4 measurement goes with them.
## Files the row would own
The id count goes from 85 to 87. 1.16 owned three files because its count stayed at 85. Like 1.13, the last version that added ids, the row owns four files:
- `docs/prd/mosaic-stack.md`
- `packages/tasks/requirements.json`: the version 1.17, the two new ids REQ-LIB-8 and REQ-LIB-9 with their titles, and the changed titles of REQ-LIB-1, REQ-LIB-2 and REQ-LIB-4. The new ids go after REQ-LIB-7 and before REQ-INST-1, in the same order as the PRD's bullets.
- `packages/tasks/tests/requirements.test.mjs`: the version pin 1.17, the id count of 87, and the duplicate-id message, which goes from `requirements[85].id: REQ-ROLE-1 twice` to `requirements[87].id: REQ-ROLE-1 twice`.
- `packages/bus/tests/requirements.test.mjs`: the list length, which goes from 85 to 87.
The gate would match 1.16's:
1. A reviewer approves the text.
2. Jason says yes to the exact text through Mos.
3. Both `requirements.test.mjs` files, in `packages/tasks/tests` and `packages/bus/tests`, and every `scripts/test-*.sh` that reads the PRD or `requirements.json` are green on Sage's gate.
4. Mos gets the landing commit.
+652
View File
@@ -0,0 +1,652 @@
# PRD 1.17 draft: Case 2 story files are DOCX (Q235 to Q237, P1 to P6)
Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 9. It is d8 plus Jason's Q41 answer O2 (DECISIONS Q239, mos-1467), which makes J-b yes, Webby's round 5 N15 to N17 (webby-0210) and Webby's round 6 B1, N19 and N20 (webby-0212) (see "d9 changes"). d8 is d7 plus Mos's d8 fix list mos-1443 (see "d8 changes"). d7 is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
- Mos's d4 check ruling mos-1372 (D1 to D14; D15 was dropped).
- mos-1374, which replaces J-c.
- The impact map mos-1368 (jason-master-restart d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`: A1 to A16, critic items C1 to C6, items 2 to 7).
- Mos's F4 measurement.
Review order (mos-1367 B5): Webby, then Mos, then Jason. Base: PRD 1.16, `docs/prd/mosaic-stack.md` blob a6db9b4a at 482e865ac.
1.16 lands first (row 291), and this text is a new version on top of it. Nothing here is built or queued. The impact map's queue changes (A24, A57 to A59) go through `scripts/mosaic queue` after 1.17 is approved (C9).
Sources, verbatim in Mos's DECISIONS:
- Q235, Jason's Q36 answer.
- Q236: chapters are DOCX, Markdown for agent files and docs.
- Q237: "It is correct for now (subject to future change)".
- The P1 to P6 reading as restated to Jason in Q38.
## Settled points (mos-1367)
These stand as ruled. d5 changes only what mos-1372 changed.
- **O1. Where a converted chapter lives.** The DOCX lives in the writing store of
O2 design D12, and the archived Markdown stays in the user's repository under a
human-marked archive folder. Ruled yes in B1, on the condition that the text
says plainly that a converted chapter leaves the Gitea-mirrored repository and
lives in the stack's writing store. mos-1372 D7 and mos-1375 add a remote for
the store, off the stack's host.
- **O2. The unit of conversion.** One story file per import, refusing a file
that is already converted. Ruled yes in B2: each file gets its own fidelity
report, human confirmation, manifest and commit.
- **O3. Whether conversion is a vocabulary action.** A human-only verb outside
the agent vocabulary, with no role grant and no agent request path. Ruled yes
in B3.
- **O4. Reading archived Markdown.** A new `archived` scope. Lookups leave it out
unless the caller asks, every answer marks it archived, and a canon citation
can't name it. Ruled yes in B4.
## d3 choices after mos-1372
- **C1 is replaced by D1, keep-source-scope.** A DOCX keeps its source's scope:
universe, book, and canon or playground. A playground DOCX follows the
playground rules and is never returned as canon. The import accepts canon and
playground story files. Type-folder files and supporting files stay Markdown
(P4). This also settles the impact map's Q18, item 4. It leaves one narrowing
for Jason, J-i. A playground DOCX can't become canon in 1.17.
- **C2 stands.** The move keeps the file's path under the archive folder:
`book1/ch3.md` goes to `<archive>/book1/ch3.md`. An archived file's universe
and book are those of its old path.
- **C3 is replaced by D3 and D14.** The archive commit is written by a new
human-only bus step (Vikunja 571) on the C2-02 write plumbing and pushed through
C2-03b. The order is the store commit and the remote's confirmation that it
holds that commit, then the archive commit and its push, then publication to the
lookups.
- **N4 stands.** REQ-LIB-8 says "a real user's books". The PRD names no user
anywhere in Case 2.
## Text changes
### Status and sources
In the Status bullet's first line, "- Status: approved, version 1.16." becomes
"- Status: approved, version 1.17." Status gains, after the two 1.16 sentences at
the end of the bullet: "Version 1.17 records Jason's rulings Q235 to Q237 of
2026-10-23: Case 2 story files are DOCX, Markdown stays for agent files and
documentation, and a human-started import converts a Markdown story file one way.
It also adds REQ-LIB-9, the Console's DOCX editor, from Jason's rulings Q195, Q226
and Q234. It changes what Case 2 requirements allow, so Mos puts the text to Jason
as written, and it lands only after his yes (mos-1366, Vikunja 570)."
New source line, above the 1.16 one: "Version 1.17 source: Jason's Q235, answering
Q36: 'How about we see a markdown file, we convert the markdown file to docx, then
archive or mark the markdown file as converted. The new standard for files in the
creating writing use case is to use docx to handle versioning and WYSIWYG. We
eliminate the potential for confusion based on the files. Agents must understand
which files are what format and where they live. The system must allow for import
and conversion. We will not run the systems in parallel. There will be an
irreversible conversion of the files.' His Q236 says: 'For creative writing
chapters, we need to utilize docx as the standard. Markdown is acceptable for
agent files, instructions, PRDs, documentation, etc. The creative writing files
should be docx for familiarity for the user.' His Q237 says 'Q38: It is correct
for now (subject to future change)', which confirms Mos's reading P1 to P6.
REQ-LIB-9 rests on his Q195 request for 'a full WYSIWYG editor within Mosaic
Stack', his Q226 answer 'F41: We want to move editing into the WYSIWYG in the
WebUI. All changes are instantly committed and available to the agents.' and his
Q234 answer 'Q37: yes' to the exact security header for the editor's frame. The
storage follows the O2 design D10 to D12 as rewritten under Vikunja 571, at
b99ca86b9 on `refactor`."
### Creative library, section lead
Replace "The library is the user's own writing repository." with "The library is
the user's own writing repository plus the stack's writing store, which holds the
DOCX story files (REQ-LIB-8, version 1.17)."
### REQ-LIB-1
Add, before "(Design 1.1": "The mapping also names one archive folder, set by the
human, where converted Markdown story files go. A book's DOCX story files are in
the stack's writing store, under the same universe and book (REQ-LIB-8)." Version
tag, inside the closing parenthesis: "Version 1.15: supporting folders.)" becomes
"Version 1.15: supporting folders. Version 1.17: the archive folder and the
writing store.)"
### REQ-LIB-2
Replace "A file's scope comes from its path alone, prefixes first: a Markdown
file under a supporting prefix is supporting, one under a playground prefix at
any level is playground, any other Markdown file in a book folder is canon, and
everything else is unmapped and never returned. A folder in both lists is
supporting." with:
"A Markdown file's scope comes from its path and from past imports, prefixes
first: a Markdown file under the archive folder is archived, one under a
supporting prefix is supporting, a Markdown file at a converted file's old path is
unmapped under REQ-LIB-8 but flagged in the format map, one under a playground
prefix at any level is playground, any other Markdown file in a book folder is
canon, and everything else is unmapped and never returned. A folder in both lists
is supporting. The archive folder follows the same placement rules and may not
overlap another prefix or a book folder. An archived file keeps the path it had
under the archive folder, and its universe and book are those of that old path. A
DOCX document in the writing store keeps its source's scope: the universe, the
book if there is one, and canon or playground. Its source is the Markdown file it
was converted from or, for one the human starts in the editor, the universe and
book or the playground the human picks (REQ-LIB-8). A playground DOCX follows the
playground rules and is never returned as canon. It stays playground after its
draft universe is made real. In version 1.17 no promotion or mapping change makes
a playground DOCX canon. A later version of this PRD says how one becomes canon."
Replace "Lookups return canon unless the caller asks for playground or
supporting." with "Lookups return canon unless the caller asks for playground,
supporting or archived. Every answer marks an archived file as archived and names
the DOCX that replaced it."
Replace "so an agent can't cite a playground or supporting file as canon" with "so
an agent can't cite a playground, supporting or archived file as canon".
In REQ-LIB-2 only, replace "The Console shows each cite's scope." with "The Console shows each cite's
scope and format, and whether a cited Markdown file was converted."
Version tag, inside the closing parenthesis: "Version 1.15: supporting.)" becomes
"Version 1.15: supporting. Version 1.17: archived scope and DOCX scope.)"
### REQ-LIB-4
Replace "The user commits and pushes with their own tools, today Obsidian with
obsidian-git." with "The user commits and pushes Markdown with their own tools,
today Obsidian with obsidian-git. A converted story file's current text is no
longer in that repository, which keeps only its archived Markdown (REQ-LIB-8)."
Add, after "Bus writes and pushes come in later rows (C2-02, C2-03b).": "A DOCX
story file is saved in the Console's editor, and each accepted save is one commit
by the human in the stack's writing store (REQ-LIB-9). Each writing-store commit
is also pushed to a remote off the stack's host, never by force-push. The remote
is a setting only a human changes, and it starts unset. A repository on the
stack's host never counts as that remote. A missing remote or a failed push never
undoes a save, and the user sees each push that is pending or failed. Only the
stack's host pushes to that remote, and no agent can reach it with any credential.
Agents read the local commit."
Replace "Agents read only the mirror's served commit and never hold a copy of
their own, so they see the user's push within about a minute." with "Agents read
the user's repository only at the mirror's served commit, except a story file
during its import under REQ-LIB-8. They read a DOCX story file only at a committed
version in the writing store, under REQ-LIB-5. They never hold a copy of their
own, so they see the user's Markdown push within about a minute and a DOCX save at
their next lookup after the editor reports it as committed."
Version tag: "and the sync items.)" becomes "and the sync items. Version 1.17,
with Q222 for the remote setting.)"
### REQ-LIB-5
Add, after "Each answer names the commit it read.": "Each answer also names the
file's format and where it lives: its path, or its document id in the writing
store. A DOCX story file is read from its latest committed save, never from
unsaved text. A lookup that starts after the editor reports a save as committed
returns that save or a later one, even while its push to the remote is pending or
has failed, and the answer names the version. Get, search and manuscript read a
DOCX story file as its extracted text with DOCX locators, never as lines. That
text is the file with its tracked changes accepted. An agent reads a story file's
comments, or its text with tracked deletions shown, only after a human turns on
the matching setting. Both settings are off by default, and only a human changes
them. The reader in this version can't read some things a DOCX story file can
hold, such as a web link, a text box, a field or hidden text. The editor warns the
human when an edit adds one, and the human can still save it. Until a later save
takes it out, lookups give agents none of the text of the part that holds it,
whether that is the chapter's body, a note or a comment. The manuscript lookup
follows the book's chapter order across both formats (O2 design D10), and the next
chapter it names is never an archived file. Links out and in resolve across both
formats. The universe lookup returns the format map (REQ-LIB-8)."
Replace "The user sees them in Obsidian's graph and backlinks." with "The user sees
a Markdown file's links in Obsidian's graph and backlinks. A DOCX story file's
wikilinks stay clickable in the Console's editor (O2 design D11), and no graph
shows them yet. The Console's Connections view lists the current links and
backlinks of each story file the user may see, in both formats, with each one's
scope and format."
Version tag: 'and "Connections are always visible".)' becomes 'and "Connections
are always visible". Version 1.17, with Q222 for the two settings and Q239 for
the Connections view.)'
### REQ-LIB-6
Replace "A reply may cite lines of the repository at a commit or pages the agent
fetched, and the broker checks every citation before it stores the message." with
"A reply may cite lines of a Markdown file at a commit, a place in a committed
DOCX story file, or pages the agent fetched, and the broker checks every citation
before it stores the message. A DOCX citation never calls a paragraph a line. It
names the document id, the committed version and a DOCX locator. A citation that
names an archived file as current is refused. The `requireCitation` setting below
applies to Markdown and DOCX citations alike, and so does the rule for a citation
marked as canon."
Add, after "the broker refuses such a write before any git call.": "The writing
store and the archive folder are read-only to a creative agent as well, and the
broker's path check treats the archive folder as story. No agent creates, edits,
deletes, converts, archives or restores a story file in any format, and no agent
writes a DOCX. No creative agent starts or undoes a conversion, and none asks for
one through a tool or a decision it raises, as REQ-LIB-8 says. An agent may still
suggest a conversion to the human in chat. Each creative role's contract states
which files are DOCX and which are Markdown, and cites a DOCX story file by its
locator, never by line."
Version tag, inside the closing parenthesis: "Version 1.15.)" becomes "Version
1.15. Version 1.17.)"
### New REQ-LIB-8
This bullet goes after REQ-LIB-7 and before the "### Installer" heading.
"- **REQ-LIB-8.** Story files become DOCX. DOCX is the standard for chapters and
the other story text in a Case 2 writing project, canon and playground alike. A
story file becomes DOCX through a human's import (below), or when the human
starts it in the editor. From then on DOCX is its only working format, edited in
the Console's editor (REQ-LIB-9). A Markdown story file stays Markdown until its
import. Files in a type folder (REQ-LIB-1), such as character and place notes,
stay Markdown. So do supporting files, agent files, instructions, PRDs and
documentation, including the character sheets, summaries and cross-links agents
keep under REQ-LIB-6.
- A converted story file's current text leaves the user's Gitea-mirrored
repository. Its DOCX lives in the stack's writing store, on the stack's host,
and not in the repository the user pushes or opens in Obsidian. Only its
archived Markdown stays there.
- An import converts one Markdown story file, canon or playground, outside every
type folder (REQ-LIB-1), to a DOCX document in the writing store. It refuses
an unmapped, supporting, archived or type-folder file, and a file that is
already converted. Each import has its own fidelity report, the human's
confirmation of that report, a manifest naming the source file and its commit,
and one commit in the store. The DOCX keeps every wiki link of the source as
written, and a lost link to another library file fails the fidelity report. A
web link becomes plain text that keeps its label and address, and the fidelity
report lists it for the human to confirm. A web link that can't be kept safely
as plain text refuses the import.
- The human can also start a new story file in the editor, in a universe and
book or a playground the human picks. It is a DOCX document in the writing
store from its first commit, with no Markdown copy, and it opens under the
same gate as the import.
- The same import marks the Markdown file converted, naming the document, and
moves it to the archive folder under its old path (REQ-LIB-1, REQ-LIB-2).
The mark and the move are one commit authored by the human in the user's
repository. A new human-only bus step (Vikunja 571) on the C2-02 write
plumbing writes it, and C2-03b pushes it. The move keeps the bytes and the
git history, so it isn't `data.delete`.
- The store commit comes first, and the writing store's remote must confirm it
holds that commit. Then come the archive commit and its push, then publication
to the lookups. Until publication the Markdown at its old path keeps its canon
or playground scope. Agents read that file at the commit the import started
from, even after the mirror's served commit reaches the archive commit or a
later one. No lookup returns the file's archived copy before publication, even
one that asks for archived files. A refusal or failure before the archive push
leaves the Markdown unmoved and no DOCX that a lookup returns. A failure after
the archive push leaves a pending import that the human sees and can resume,
never a claim that nothing moved. An archive push whose result is unknown
counts as a failure after the archive push until the stack reads the result
back from the user's remote.
- Each stage of an import writes a logged event. The import's record on the
stack's host names the source file's path, commit and blob and the new
document id, and each event points to that record.
- After a conversion, a Markdown file at the converted file's old path is
neither canon nor playground. Lookups leave it out, the format map flags it,
and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
canon until its import, unless it sits at a converted file's old path or under
a playground prefix. The Console flags it and offers the import. Nothing
converts it on its own.
- A hybrid's exclusion that covers a converted file's old path also covers its
DOCX document (REQ-LIB-3). An exclusion of a whole book folder also covers
every DOCX document in that book, including one started in the editor. An
exclusion can also name a DOCX document by its id.
- Only a human starts an import or a new story file, from the CLI or a Console
session signed in as that human. Both are human verbs outside the agent action
vocabulary. Neither is added to `ACTIONS` in the business vocabulary or to the
broker's always-gated list, including the import's action, `library.convert`.
No role holds them, no agent can request them, and the hard-wait list in
REQ-WAIT-1 doesn't change. In an organization with several humans, who may
start them is a setting only a human changes, and the default is the owner
only.
- Conversion is one way, with no parallel run. Neither the Console nor any agent
edits a converted story file as Markdown, and no DOCX edit syncs back to
Markdown. The Console's Markdown save refuses every file under the archive
folder. The user's own tools can still change the archived Markdown, and no
such change reaches the DOCX. The old Markdown stays in git history, and
nothing rewrites history. A promotion into a book folder (C2-19) runs the
import for a Markdown story file or is refused. A promotion of a playground
DOCX is refused in version 1.17.
- Agents get a format map: for each mapped folder and story file, its format,
scope and where it lives, archived files, flagged files and pending imports
included.
- The import and new story files ship closed. They open only after four things.
The editor ships. The human has set the writing store's remote, and the stack
has checked that it can push there. Agents can read committed DOCX story files
through the lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent
system that writes the user's files has stopped, which for Case 2 is the
Hermes retirement (#423 step 5). So the import itself never cuts agents off
from a chapter, the chapter's current text never lives only on the stack's
host, and no second system writes the story. The first import of a real user's
books waits for Jason's go.
(Q235, Q236, Q237: P1 to P6. Q222 for the setting. O2 design D10 to D12.
Version 1.17.)"
### New REQ-LIB-9
This bullet goes after REQ-LIB-8.
"- **REQ-LIB-9.** The Console's DOCX editor. The human edits a DOCX story file in
the Console's editor, in a split pane with the agent chat alongside it. The
human can pin an agent chat to a document. The human can select a word,
right-click it, and choose a synonym from a thesaurus. Only a human saves. Saves
happen on their own while the human types (Q226), at intervals only a human
changes, and each accepted save is one commit with the human as author, pushed
as REQ-LIB-4 says. A save made against an older version than the current one is
refused. Agents never write a DOCX. Versioning is the editor's tracked changes
plus one git commit per save. The editor's code is vendored and pinned, with a
hash per file and the licenses that its independent security review accepted. At
runtime the editor loads only files that the stack itself serves, and it
requests nothing from any other host. The editor's frame sends exactly the
content security policy Jason approved in Q234, and the WebUI shell's own header
is unchanged. REQ-PROJ-3's project docs editor is unchanged. (Q195, Q226, Q234,
Q236. O2 design D3, D4, D6, D8, D10, D13, D14, D16 and D22. Version 1.17.)"
### Version 1.17 notes
Added after the 1.16 notes:
"- Version 1.17:
- Jason confirmed the reading 'for now (subject to future change)' (Q237). The
rulings stand until he revises them.
- The 10-27 Case 2 release ships on Markdown as built (P6). The 1.11 line 'The
Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG editor (Q195,
Q196) is an add-on that doesn't gate it.' still holds. Conversion opens after
10-27, and only once REQ-LIB-8's four conditions are met. The editor ships,
the writing store's remote is set and the stack has checked that it can push
there, agents can read and cite committed DOCX story files through the
lookups, and Hermes is retired, which is #423 step 5.
- The 1.12 note says five bus write and Console editor rows, C2-02, C2-03b,
C2-11a, C2-11b and C2-19, don't gate 10-27. Jason's Q226 F42 later moved
C2-02, C2-03b and C2-11b into the 10-27 slice. The Case 2 design brings C2-11a
in with them, because C2-11b needs it. C2-19 stays outside the slice. Mos's
reading of F42 is that C2-02 and C2-03b now gate 10-27, because design 9.2
puts them in 'the smallest slice that lets her start'. C2-11a and C2-11b are
slice rows that still don't gate 10-27, because the editor is the Q195 add-on.
The DOCX change in 1.17 leaves this slice as it is, and C2-11b's Markdown
editor never edits a converted story file.
- Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9). The
1.12 note that the Case 2 design keeps Obsidian now holds only for Markdown
files, including story files not yet imported. A DOCX story file is edited
only in the Console's editor under REQ-LIB-9.
- This replaces two parts of the O2 design at 25c09f19f. The superseded-notice
proposal from Fenton's F1 in D12 is withdrawn, and the
keep-Markdown-at-its-path model of D11 is replaced. Gyro's rewrite of the O2
design for Vikunja 571, landed on `refactor` at b99ca86b9, carries the
details, and where it differs from this text, this text holds:
- the import and archive steps, the mark, and the new bus step;
- links from supporting files to a moved chapter;
- the DOCX reader;
- the user's unpushed or later changes to an archived file or at its old path,
and pushing and closing the file in Obsidian before each import;
- the writing store's remote in W4 section 7.3, which replaces the no-remote
and no-backup rule that W4 section 7.4 held before the rewrite, at
062646487;
- import events counted by a bus restore, and recovery when a bus restore or a
restore of the whole host predates an import;
- manifest order for the manuscript lookup and its next chapter;
- `library.convert` kept out of `ACTIONS`, out of the hard-wait note and out
of the pinned `GATED_ONLY` list in packages/business/src/vocabulary.mjs.
- Agents read a converted story file through the Case 2 lookups under the same
project and universe access as Markdown canon, in any thread or room. W4's
earlier O2-only conversations, per-document grants and per-send sharing don't
gate these lookups. The 571 rewrite replaced them in W4 sections 7.1 and 7.2.
The creative tool list in REQ-LIB-6 doesn't change.
- The six creative role contracts row 213 landed say 'Every item cites the
lines of the user's material it is about'. A build row under 571 changes that
line for DOCX story files (REQ-LIB-6).
- A new Markdown file in a book folder is still canon in 1.17. The 1.17
exception is a file at a converted file's old path, which REQ-LIB-2 makes
unmapped.
- Story files convert whether canon or playground. Type-folder and supporting
files stay Markdown.
- Measured 2026-10-23 at about 01:21Z: the Case 2 user's library repository
tracks 213 files, 203 of them Markdown, and none is a .docx or .doc. It has no
untracked files. The user's .doc files (Q196) are outside it. So the landed
code has no DOCX there to report as unreadable at 10-27.
- Text replaced in 1.17, kept here as in earlier notes:
- Creative library lead, the 1.16 sentence: "The library is the user's own
writing repository."
- REQ-LIB-2, the 1.16 sentences: "A file's scope comes from its path alone,
prefixes first: a Markdown file under a supporting prefix is supporting,
one under a playground prefix at any level is playground, any other
Markdown file in a book folder is canon, and everything else is unmapped
and never returned. A folder in both lists is supporting." "Lookups return
canon unless the caller asks for playground or supporting." "so an agent
can't cite a playground or supporting file as canon" "The Console shows
each cite's scope."
- REQ-LIB-4, the 1.16 sentences: "The user commits and pushes with their own
tools, today Obsidian with obsidian-git." "Agents read only the mirror's
served commit and never hold a copy of their own, so they see the user's
push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
stood at conversion, and the Console's Connections view shows their
current links.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
- REQ-LIB-1 only gains sentences.
- REQ-LIB-3's text and REQ-LIB-7 are unchanged. REQ-LIB-8 adds one form of
exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. A path
exclusion also covers DOCX documents, through a converted file's old path or a
whole book folder. An archived file and a DOCX document keep a universe, and a
book where their source has one, so every lookup still names one universe.
REQ-PROJ-3, the Console editor for a project's spec and docs, is unchanged.
C2-11b opens the library's Markdown files in that same editor.
- In `packages/tasks/requirements.json` the titles of REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 change, and those of REQ-LIB-5 and REQ-LIB-6 stay. REQ-LIB-1 becomes
'A human-set library maps the user's repository onto universes, books, types,
playgrounds, supporting and archive folders'. REQ-LIB-2 becomes 'Scope comes
from the path or a DOCX's source, lookups default to canon, and only canon
files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change. The
bus fast-forwards a read-only mirror every 60 seconds, the one place agents
read Markdown'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes from 85
to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only
one-way import or in the editor; Markdown stays for notes and docs', and
REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one
commit, and the editor code is vendored and pinned'.
- Open points O1 to O4 of draft 1 were ruled by Mos in mos-1367 B1 to B4.
- Sage files the build rows under Vikunja 570 and 571 after the rewrite is
approved. This amendment adds the requirements only."
### Risks
The new lines go at the end of "## Risks and open questions", after the bullet
that begins "Trusted mode lets an agent" and before the "## Milestones" heading.
- "A DOCX file is a zip archive, so git can't show a line diff of a chapter.
Review of a story change goes through the editor and the committed reader,
not `git diff`. (Version 1.17.)"
- "A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
chapter only through its archived Markdown, and no DOCX edit reaches that file's
links. The DOCX's current links show in the Console's editor and its
Connections view, and no graph shows them yet. (Version 1.17.)"
- "Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
confirmation, an untouched original on refusal, and the kept archive.
(Version 1.17.)"
- "A restore of the stack's bus alone from a backup taken before an import drops
the import's logged events, while the archive commit and the DOCX stay. A
restore of the stack's whole host from such a backup also takes the writing
store and the host's import records back to before the import. The archive
commit stays in the user's Gitea repository and the DOCX stays on the writing
store's remote, but the host keeps no record of the import. A bus restore counts
import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)"
## d9 changes (Q239 and Webby round 5)
- J-b yes (Q239, mos-1467), in the five edits mos-1443 R3 set for a yes (Webby round 6 B1). R3.1: REQ-LIB-5's replacement gains the Connections view sentence after "no graph shows them yet.". R3.2: the graph Risks line ends with the editor and the Connections view. R3.3: the A6 line moves from "Not taken" under A6. R3.5: the REQ-LIB-5 note ends with the Connections view, which drops "until a Console view of their links exists" and "Q226 records that view as a later option". R3.4 is Gyro's design edit at 767bb62c7 (writing-editor.md:188-190, :240-244 and :1272, docx-citations.md:66), and it follows in his docs round. Beyond these and N21, nothing else in the PRD text changes.
- N19 and N20 (Webby round 6): J-b's question text is in the past tense, and the line that the import doesn't wait for the view is cited as Mos's R3 default. Mos confirmed it at his J-b check (mos-1470).
- N21 (Webby round 6, Mos yes in mos-1470): REQ-LIB-5's version tag cites Q239 for the Connections view beside Q222 for the two settings.
- N15: the D7 landing line gives R8's wording beside the impact map's quote.
- N16: the d7 changes line notes that R8 replaces "no agent credential reaches it" in d8.
- N17: the REQ-LIB-2 Replace quote and the REQ-LIB-8 old-path bullet are rewrapped under 82 columns. Their words are unchanged.
## d8 changes (Mos's d8 fix list, check wf_63bba879-180)
- R1: REQ-LIB-4's tail covers DOCX saves, which agents see at their next lookup after the editor reports them as committed. The notes quote the whole 1.16 sentence. Source: nit/jason/F10.
- R2: REQ-LIB-4's new title says the mirror is "the one place agents read Markdown". Source: decisions.n14.
- R3: J-b asks whether 1.17 requires the Console Connections view, with Mos's recommendation of yes. Sources: mos1372/F9, jason/F2, anchors/F5.
- R4: C2-02 and C2-03b gate 10-27, C2-11a and C2-11b are slice rows that don't, and "F42 wins" is gone. New J-k. Sources: anchors/F6, jason/F5.
- R5: where the landed 571 design differs, this text holds. Sources: anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6, G16's design follow-on.
- R6: `library.convert` stays out of `ACTIONS`, and REQ-WAIT-1's hard-wait list doesn't change. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
- R7: a repository on the stack's host never counts as the writing store's remote, and Item 2 says so. Sources: mos1372/F2, adversarial/F1, gyro571/F2.
- R8: "no agent credential reaches it" becomes "no agent can reach it with any credential". Source: Fenton's B1 under mos-1379.
- R9: no playground DOCX becomes canon in 1.17, and a promotion of one is refused. New J-i. Sources: mos1372/F8, adversarial/F2.
- R10: REQ-LIB-9 restates Q195's pin and right-click thesaurus, and its cite gains D4 and D22. Source: critic/C4.
- R11: REQ-LIB-9's license bar is the licenses its independent security review accepted. New J-l. Source: critic/C3.
- R12: no text for D16's narrow-window tabs. Mos records that reading in STATE. Sources: jason/F1, second verifier vote.
- R13: no lookup returns a file's archived copy before publication. Source: adversarial/F5 part 3.
- R14: a restore of the whole host from a backup taken before an import is a fourth recovery case. Source: adversarial/F6.
- M1: REQ-LIB-4's writing-store sentences follow the later bus rows sentence and add the off-host remote rules and DOCX read timing. The note quotes both 1.16 sentences. Sources: mos1372/F2, adversarial/F1, gyro571/F2, anchors/F4, nit/jason/F10, adversarial/F5 part 1, Fenton B1.
- M2: REQ-LIB-8's order bullet adds the remote's confirmation, the old-path read rule and "archive push". Sources: mos1372/F2 and the other remote findings, mos1372/F7, adversarial/F5 parts 2 to 4.
- M3: REQ-LIB-8's gate bullet has four conditions, adding the remote. Sources: the remote findings, critic/C1 and C2 edit 2.
- M4: the 1.17 note on when conversion opens restates the four conditions and drops W4b. Sources: jason/F5, the remote findings.
- M5: Item 2 rewritten with the remote rules. Sources: the remote findings.
- M6: the 1.12 gating note rewritten. Sources: anchors/F6, jason/F5.
- M7: REQ-LIB-9 gains the split pane, the pin, the thesaurus, the license bar and the runtime load rule, and cites more of the design. Sources: jason/F1, critic/C4, critic/C3, nit/anchors/F12, nit/gyro571/F9.
- M8: REQ-LIB-2, the one-way bullet and the C1 note keep a playground DOCX out of canon in 1.17. Sources: mos1372/F8, adversarial/F2.
- M9: REQ-LIB-5 says what the reader can't read, and the import turns web links into plain text. New J-j. Sources: critic/C1, critic/C2.
- M10: an exclusion of a whole book folder covers that book's DOCX documents. Source: adversarial/F3, with B5.
- M11: the human-verbs sentence and the 571 list item name `library.convert` and REQ-WAIT-1. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
- M12: J-b rewritten. Sources: mos1372/F9, jason/F2, anchors/F5, gyro571/F3, critic/C6.
- M13: J-d puts the agent read model to Jason as his call. Source: mos1372/F1.
- M14: Status moves to version 1.17 and names REQ-LIB-9. Sources: anchors/F2, mos1372/F4, critic/C7.
- M15: the 1.17 source line quotes Q235 in full (mos-1446) and the REQ-LIB-9 sources, at b99ca86b9. Sources: critic/C5, anchors/F9, jason/F8 pin part, nit/gyro571/F8, critic/C6 pin part.
- M16: the 571 list lead names the two replaced design parts and the landed design. Sources: anchors/F9, anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6.
- Landing swap: Gyro's docs round for R5, R6 and R14 landed as b99ca86b9 (Fenton fenton-0015), so M15 and M16 cite b99ca86b9 in place of 767bb62c7, as the d8 fix list's header says. J-b keeps 767bb62c7 for its line numbers.
- M17: W4 section numbers in the remote item and the lookups note. Sources: gyro571/F5, nit/adversarial/F11.
- M18: the D7 landing line. Sources: gyro571/F5, the remote findings.
- M19: a logged event for each import stage, in REQ-LIB-8 and A2. Source: gyro571/F7.
- M20: the Risks line on restores adds the whole-host case. Sources: adversarial/F6, gyro571/F7.
- M21: the 571 restore item adds the whole-host case. Source: adversarial/F6.
- M22: the graph Risks line and the REQ-LIB-5 note name a Console view. Sources: anchors/F5, jason/F2.
- M23: the d6 B3 line drops "F42 wins". Source: anchors/F6.
- M24: new Markdown in a converted book, with the old-path and playground exceptions. Source: mos1372/F6.
- M25: J-c's last sentence is Mos's reading. Source: mos1372/F10.
- M26: J-h rewritten with the D13 defaults. Source: jason/F3.
- M27: Item 4 rewritten. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
- M28: the "Not taken" note on unmarked folders. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
- M29: the import refuses an unmapped file. Sources: gyro571/F4, decisions.unmarked_folders.
- M30: placement notes for REQ-LIB-8, REQ-LIB-9 and the Risks lines. Sources: anchors/F3, nit/anchors/F14.
- M31: REQ-LIB-4's title and the D11 landing line. Source: decisions.n14.
- M32: the Q236 note limits the 1.12 Obsidian note to Markdown files. Source: critic/C8.
- M33: the exclusion note adds path exclusions of DOCX documents. Source: anchors/F7.
- M34: A9 moves from "Mine" to the impact-map list. Source: decisions.exclusion_by_id.
- M35: the D5 landing line. Sources: anchors/F10, mos1372/F5, nit/adversarial/F12.
- M36: the D9 and D14 landing lines. Source: mos1372/F1.
- M37: the O1 and C3 notes add the off-host remote and its confirmation. Sources: the remote findings.
- M38: the REQ-PROJ-3 note. Source: nit/anchors/F11.
- M39: REQ-LIB-5's freshness sentence keys on the editor reporting a save as committed. Source: nit/adversarial/F9.
- M40: REQ-LIB-6 bars agent requests for a conversion and allows a suggestion in chat. Source: nit/jason/F9.
- M41: REQ-LIB-2's opening and old-path rule, and the "Mine" note. Source: nit/adversarial/F10.
- M42: REQ-LIB-8's lead sentence on Markdown story files. Source: nit/mos1372/F12.
- M43: REQ-LIB-6's citation sentence. Source: nit/anchors/F13.
- M44: REQ-LIB-5's version tag uses plain quotes. Source: nit/anchors/F15.
- M45: reference codes B5, D12, D10 and B4 name their source. Source: nit/mos1372/F11.
- Files the row would own: four files, with the id count going to 87. Sources: anchors/F1, adversarial/F4, decisions.n14 F4.
## d7 changes (mos-1379, Fenton's 571 B2)
- REQ-LIB-5 gains Mos's three sentences after the extracted-text sentence. The text agents read has tracked changes accepted. Comments, and text with tracked deletions shown, each wait for a setting only a human turns on, off by default. Its version tag cites Q222 for the two settings, which is the only text here beyond Mos's.
- J-d gains Mos's sentence on comments and tracked deletions.
- Fenton's B1 is design only. d6's "no agent credential reaches it" holds (R8 replaces it in d8), and the shared helper stays #431.
## d6 changes (Webby round 3, webby-0207)
- B1: titles for REQ-LIB-8 and REQ-LIB-9 in the requirements.json note and the file list. REQ-LIB-9 takes Webby's title. REQ-LIB-8 keeps mine, because Webby's ("...one-way import that archives the Markdown under its old path") leaves out D2's story files started in the editor.
- B2: REQ-LIB-2 makes a Markdown file at a converted file's old path unmapped, ahead of the playground and canon rules, so a playground conversion is covered too. REQ-LIB-8's old-path bullet says "neither canon nor playground".
- B3: the slice note names Q226 F42's three rows (C2-02, C2-03b, C2-11b). C2-11a joined the slice with C2-11b, because C2-11b needs it. Both are slice rows that don't gate 10-27, as design 9.2, case2-rows.md:1655 and the impact map's A57 say. The 'F42 wins' sentence is gone from the note in d8, so no queue change follows from it.
- N10: the cite-scope replacement is scoped to REQ-LIB-2. The 1.14 note's copy stays.
- N11: the notes say REQ-LIB-8 adds the by-id exclusion form REQ-LIB-3 doesn't have.
- N12: J-h marks the autosave intervals for Jason.
- N13: J-b names REQ-LIB-5's "Connections are always visible" tag and Q226's "another way to handle the visual brain map".
- N14, not taken: REQ-LIB-4's title stays internal F3's text, as mos-1372 D11 ruled. Webby's alternative, "...and agents read Markdown from it alone" (120 chars), is Mos's call.
## Where each mos-1372 item landed
- D1: REQ-LIB-2's DOCX sentences, the REQ-LIB-8 lead and import bullet, C1 above, and the notes. "Playground files stay Markdown in 1.17" is gone.
- D2: REQ-LIB-2's "for one the human starts in the editor" and the REQ-LIB-8 new-story-file bullet. The universe-and-book note is now true.
- D3: the REQ-LIB-8 order bullet. This also settles N8.
- D4: REQ-LIB-8's one-way bullet, which carries the archive refusal and consequence F2's text, and the 571 list item.
- D5: it landed in REQ-LIB-2's "a Markdown file at a converted file's old path is unmapped" in Webby's B2 form, and in the REQ-LIB-8 old-path bullet.
- D6: REQ-LIB-5's freshness sentences.
- D7 and mos-1375: REQ-LIB-4's remote sentences, the remote step in REQ-LIB-8's order bullet, the fourth opening condition and the 1.17 note on when conversion opens, plus the 571 list item on W4 section 7.3, which replaces the no-remote rule of the old W4 section 7.4. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it", which R8 words in d8 as "no agent can reach it with any credential".
- D8: the REQ-LIB-5 replacement, the import bullet's wiki-link sentence, the Risks line, the note quoting the old sentence, and "REQ-LIB-1 only gains sentences".
- D9: the 1.17 note that agents read a converted story file through the Case 2 lookups, from the d4 check's consistency B2, and J-d, which puts the read model to Jason as his call.
- D10: the d4 check's B5 sentence, in REQ-LIB-8.
- D11: internal F3's clause in the REQ-LIB-4 section, and in the notes the title as Mos amended it for N14.
- D12: the whole 1.16 sentence replaced, and quoted in full in the notes.
- D13: consequence F1's text, in REQ-LIB-4 and REQ-LIB-8's first bullet.
- D14: the row text from the d4 check's consistency B1, in the mark-and-move bullet and C3 above.
## What d5 adds beyond mos-1372 (for Webby and Mos to check)
From the impact map, folded as mos-1368 item 1 asked:
- A1: the Console shows a cite's format and converted state (REQ-LIB-2).
- A2: the logged events and the several-humans setting, default owner only, under Q222.
- A3: the import is outside the vocabulary, the hard-wait list is unchanged, and the archive move isn't `data.delete`. The C6 code site is in the 571 list.
- A4: the section lead and REQ-LIB-1's writing-store sentence.
- A6: format and location in every answer, extracted text with DOCX locators, links across formats.
- A6's named chapter-links view. D8 took B8's "no graph shows them yet", and J-b put the view to Jason. His O2 (Q239) takes it in d9 as REQ-LIB-5's Connections view (mos-1443 R3).
- A7: read-only extends to the store and the archive, no agent action on a story file in any format, no agent DOCX write, and role contracts state the formats. C1's role contract line is a 571 build item.
- A8: the DOCX cite shape, and a cite naming an archived file as current is refused.
- A9: a hybrid sees a source's DOCX canon through REQ-LIB-2's source scope, and REQ-LIB-8 lets an exclusion name a DOCX document by its id. The id form lets the human exclude a single DOCX started in the editor, which has no old path. An archived file isn't canon, so no hybrid sees it.
- A10: REQ-LIB-9, so the count is 87, not 86.
- A11: the Hermes gate, from item 3.
- A12: the C2-19 promotion sentence.
- A13: the broker's path check treats the archive folder as story.
- A14 and C5: two Risks lines.
- A15: the flagged-new-file bullet. It sits beside the note from the d4 check's B4, "a new Markdown file in a book folder is still canon". The file stays canon and is offered for import.
- A16: three titles change and two stay.
- C2: manifest order for the manuscript lookup.
- F3: the C2-02 gating note.
- F4: Mos's measurement.
Mine, beyond either source:
- REQ-LIB-2 opens "A Markdown file's scope comes from its path and from past imports". DOCX scope comes from the source, so "a file's" would be false.
- REQ-LIB-9's autosave sentence. It is Mos's reading of F41's "instantly committed", with human-set intervals. The exact intervals stay in the design.
Not taken:
- Impact map item 4's "files in unmarked folders are eligible one at a time", for folders outside every book folder and playground only. Their files are unmapped under REQ-LIB-2, so a DOCX made from one would have no universe or book to keep. The human maps such a folder into a book folder or a playground first, under REQ-LIB-1. A file in an unmarked folder inside a book folder or a playground is canon or playground, and the import takes it one file at a time.
## Marked for Jason (additions in the text he sees)
- J-a. Whether "We will not run the systems in parallel" applies per chapter or as one switch per book. His call. As written, it applies per chapter.
- J-b. A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the chapter only through its archived Markdown, and no DOCX edit reaches that file's links. REQ-LIB-5 still cites two TARGET lines, "Visualization of data via Obsidian or equivalent is required" and "Connections are always visible". Mos's reading was that 1.17 as d8 wrote it met neither line for converted chapters. Their current links showed to the user only as clickable text inside the editor, and no graph or list showed them. Q226 leaves room for "another way to handle the visual brain map". The O2 design that landed for Vikunja 571 at 767bb62c7 held a Console Connections view and its tests as later work that waited for this answer. His call was whether 1.17 requires that view. As d8 was written, it did not. Mos recommended that it does, because impact map A6 says "Recommended: name the view" and TARGET says "Connections are always visible". Jason answered O2 (Q239): 1.17 requires the view. d9 writes it into REQ-LIB-5, its note and the Risks line, in the text of mos-1443 R3. The view comes with the conversion work after 10-27, and the 10-27 Case 2 release stays on Markdown (Q235 P6, Q237). That the import does not wait for the view is Mos's default in mos-1443 R3. Q239 doesn't address it, and Mos confirmed it at his J-b check (mos-1470).
- J-c (mos-1374): "The store pushes to a remote (D7), and the Case 2 VM is in the nightly PBS backup. For a fixed restore point before her first real import, a manual backup of VM 1012 with PBS protect runs first. That step is reversible and needs no reboot." Mos's reading is that this sits inside the P5 go step, so it needs no new question.
- J-d. How agents read a converted chapter, D9. His call. As written, an agent reads it through the ordinary Case 2 lookups in any thread or room, under the same project and universe access as Markdown canon. This is Mos's reading of F41 and REQ-LIB-7. The other choice is the model in the earlier O2 design, W4 sections 7.1 and 7.2. Under it, an agent reads a converted chapter only in a new writing conversation that holds one human and one agent, only after the business owner grants that agent access to that document, and by default only what the human attaches to each message. Comments and tracked deletions stay hidden from agents until a human turns on a setting for each. Markdown has neither, so the default keeps agents at the access Markdown gives them. Those two sentences are Mos's ruling mos-1379. A yes to the text as written settles it.
- J-e. Versioning means the editor's tracked changes plus one git commit per save.
- J-f. A converted chapter can be edited only in the Console while the stack is reachable. Obsidian no longer edits it offline.
- J-g. The import takes Markdown only. Importing .doc files stays a separate task.
- J-h. Autosave under REQ-LIB-9. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." Mos's reading, recorded with that ruling, is "Each save is a commit at once, and agents read it at their next lookup." At the O2 design's D13 defaults, a save commits at most 2 seconds after a pause in typing or 15 seconds into steady typing, with no human action needed. Mos's reading in mos-1359 counts that delay as "instantly committed". Only a human changes the intervals. The PRD names no values, and the defaults stay in the design. Whether a delay of seconds fits his word "instantly" is his call, so this is marked for him.
- J-i. In 1.17 a playground story that is DOCX can't become canon. That covers a playground Markdown file the human imports, a story the human starts in a playground in the editor, and the DOCX of a draft universe after it is made real. Today Melanie promotes a playground file by moving it into a book folder in Obsidian, and a DOCX in the writing store can't be moved that way. She can start a new story in the book and paste the text in, but it gets a new document id and the playground copy keeps the history. A later version adds DOCX promotion with C2-19, which comes after the slice.
- J-j. Web links, text boxes and fields in a converted chapter. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." The reader in this version can't read a web link, a text box, a field or hidden text. As written, the import turns each web link into plain text that keeps its label and address, and the human confirms that in the fidelity report. A web link that can't be kept safely as plain text refuses the import. If a later edit adds one of these, the save keeps it and the editor warns the human. Agents then get none of that part's text until a save takes it out.
- J-k. The 1.12 note Jason approved says bus writes, C2-02 and C2-03b, don't gate 10-27. His Q226 "F42: ok" moved them into the 10-27 slice, and design 9.2 counts them in the smallest slice that lets Melanie start. Mos reads that as making them gate 10-27. The editor rows C2-11a and C2-11b still don't gate it, per his Q195 words "we need to get it moving as an add-on".
- J-l. REQ-LIB-9 does not use REQ-PROJ-3's "permissive license" bar. It takes the licenses that the editor's independent security review accepted, because that review cleared fonts under the SIL Open Font License and the Ubuntu Font License, which not every reader counts as permissive. Mos's reading.
Marked as changes beyond P1 to P6, from the impact map (Q17, item 5):
- A1 (archived scope and canon cites)
- A2 (the conversion requirement, REQ-LIB-8)
- A3 (outside the vocabulary, hard-wait list unchanged)
- A6 (lookups across formats)
- A7 (agent bounds)
- A8 (DOCX cites)
- Item 2 and mos-1375: each writing-store commit is pushed to a remote off the stack's host. Only a human sets it, it starts unset, and a repository on the stack's host doesn't count. Only the stack's host pushes to it, and no agent can reach it with any credential. Import and new story files stay closed until the remote is set and checked, and each import's archive push waits until the remote holds the DOCX commit. This keeps "Git is used for all changes -- local and remote" true for DOCX chapters.
- Item 3 (the Hermes gate)
- Item 4. Canon and playground story files convert one file at a time, including files in unmarked folders inside a book folder or a playground. Type-folder and supporting files stay Markdown. A story file outside every book folder and playground has no universe or book, so it converts only after a human maps its folder into a book folder or a playground. The mapping step is Mos's reading of P1 and Q30.
The F4 measurement goes with them.
## Files the row would own
The id count goes from 85 to 87. 1.16 owned three files because its count stayed at 85. Like 1.13, the last version that added ids, the row owns four files:
- `docs/prd/mosaic-stack.md`
- `packages/tasks/requirements.json`: the version 1.17, the two new ids REQ-LIB-8 and REQ-LIB-9 with their titles, and the changed titles of REQ-LIB-1, REQ-LIB-2 and REQ-LIB-4. The new ids go after REQ-LIB-7 and before REQ-INST-1, in the same order as the PRD's bullets.
- `packages/tasks/tests/requirements.test.mjs`: the version pin 1.17, the id count of 87, and the duplicate-id message, which goes from `requirements[85].id: REQ-ROLE-1 twice` to `requirements[87].id: REQ-ROLE-1 twice`.
- `packages/bus/tests/requirements.test.mjs`: the list length, which goes from 85 to 87.
The gate would match 1.16's:
1. A reviewer approves the text.
2. Jason says yes to the exact text through Mos.
3. Both `requirements.test.mjs` files, in `packages/tasks/tests` and `packages/bus/tests`, and every `scripts/test-*.sh` that reads the PRD or `requirements.json` are green on Sage's gate.
4. Mos gets the landing commit.
+652
View File
@@ -0,0 +1,652 @@
# PRD 1.17 draft: Case 2 story files are DOCX (Q235 to Q237, P1 to P6)
Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 9. It is d8 plus Jason's Q41 answer O2 (DECISIONS Q239, mos-1467), which makes J-b yes, Webby's round 5 N15 to N17 (webby-0210) and Webby's round 6 B1, N19 and N20 (webby-0212) (see "d9 changes"). d8 is d7 plus Mos's d8 fix list mos-1443 (see "d8 changes"). d7 is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
- Mos's d4 check ruling mos-1372 (D1 to D14; D15 was dropped).
- mos-1374, which replaces J-c.
- The impact map mos-1368 (jason-master-restart d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`: A1 to A16, critic items C1 to C6, items 2 to 7).
- Mos's F4 measurement.
Review order (mos-1367 B5): Webby, then Mos, then Jason. Base: PRD 1.16, `docs/prd/mosaic-stack.md` blob a6db9b4a at 482e865ac.
1.16 lands first (row 291), and this text is a new version on top of it. Nothing here is built or queued. The impact map's queue changes (A24, A57 to A59) go through `scripts/mosaic queue` after 1.17 is approved (C9).
Sources, verbatim in Mos's DECISIONS:
- Q235, Jason's Q36 answer.
- Q236: chapters are DOCX, Markdown for agent files and docs.
- Q237: "It is correct for now (subject to future change)".
- The P1 to P6 reading as restated to Jason in Q38.
## Settled points (mos-1367)
These stand as ruled. d5 changes only what mos-1372 changed.
- **O1. Where a converted chapter lives.** The DOCX lives in the writing store of
O2 design D12, and the archived Markdown stays in the user's repository under a
human-marked archive folder. Ruled yes in B1, on the condition that the text
says plainly that a converted chapter leaves the Gitea-mirrored repository and
lives in the stack's writing store. mos-1372 D7 and mos-1375 add a remote for
the store, off the stack's host.
- **O2. The unit of conversion.** One story file per import, refusing a file
that is already converted. Ruled yes in B2: each file gets its own fidelity
report, human confirmation, manifest and commit.
- **O3. Whether conversion is a vocabulary action.** A human-only verb outside
the agent vocabulary, with no role grant and no agent request path. Ruled yes
in B3.
- **O4. Reading archived Markdown.** A new `archived` scope. Lookups leave it out
unless the caller asks, every answer marks it archived, and a canon citation
can't name it. Ruled yes in B4.
## d3 choices after mos-1372
- **C1 is replaced by D1, keep-source-scope.** A DOCX keeps its source's scope:
universe, book, and canon or playground. A playground DOCX follows the
playground rules and is never returned as canon. The import accepts canon and
playground story files. Type-folder files and supporting files stay Markdown
(P4). This also settles the impact map's Q18, item 4. It leaves one narrowing
for Jason, J-i. A playground DOCX can't become canon in 1.17.
- **C2 stands.** The move keeps the file's path under the archive folder:
`book1/ch3.md` goes to `<archive>/book1/ch3.md`. An archived file's universe
and book are those of its old path.
- **C3 is replaced by D3 and D14.** The archive commit is written by a new
human-only bus step (Vikunja 571) on the C2-02 write plumbing and pushed through
C2-03b. The order is the store commit and the remote's confirmation that it
holds that commit, then the archive commit and its push, then publication to the
lookups.
- **N4 stands.** REQ-LIB-8 says "a real user's books". The PRD names no user
anywhere in Case 2.
## Text changes
### Status and sources
In the Status bullet's first line, "- Status: approved, version 1.16." becomes
"- Status: approved, version 1.17." Status gains, after the two 1.16 sentences at
the end of the bullet: "Version 1.17 records Jason's rulings Q235 to Q237 of
2026-10-23: Case 2 story files are DOCX, Markdown stays for agent files and
documentation, and a human-started import converts a Markdown story file one way.
It also adds REQ-LIB-9, the Console's DOCX editor, from Jason's rulings Q195, Q226
and Q234. It changes what Case 2 requirements allow, so Mos puts the text to Jason
as written, and it lands only after his yes (mos-1366, Vikunja 570)."
New source line, above the 1.16 one: "Version 1.17 source: Jason's Q235, answering
Q36: 'How about we see a markdown file, we convert the markdown file to docx, then
archive or mark the markdown file as converted. The new standard for files in the
creating writing use case is to use docx to handle versioning and WYSIWYG. We
eliminate the potential for confusion based on the files. Agents must understand
which files are what format and where they live. The system must allow for import
and conversion. We will not run the systems in parallel. There will be an
irreversible conversion of the files.' His Q236 says: 'For creative writing
chapters, we need to utilize docx as the standard. Markdown is acceptable for
agent files, instructions, PRDs, documentation, etc. The creative writing files
should be docx for familiarity for the user.' His Q237 says 'Q38: It is correct
for now (subject to future change)', which confirms Mos's reading P1 to P6.
REQ-LIB-9 rests on his Q195 request for 'a full WYSIWYG editor within Mosaic
Stack', his Q226 answer 'F41: We want to move editing into the WYSIWYG in the
WebUI. All changes are instantly committed and available to the agents.' and his
Q234 answer 'Q37: yes' to the exact security header for the editor's frame. The
storage follows the O2 design D10 to D12 as rewritten under Vikunja 571, at
b99ca86b9 on `refactor`."
### Creative library, section lead
Replace "The library is the user's own writing repository." with "The library is
the user's own writing repository plus the stack's writing store, which holds the
DOCX story files (REQ-LIB-8, version 1.17)."
### REQ-LIB-1
Add, before "(Design 1.1": "The mapping also names one archive folder, set by the
human, where converted Markdown story files go. A book's DOCX story files are in
the stack's writing store, under the same universe and book (REQ-LIB-8)." Version
tag, inside the closing parenthesis: "Version 1.15: supporting folders.)" becomes
"Version 1.15: supporting folders. Version 1.17: the archive folder and the
writing store.)"
### REQ-LIB-2
Replace "A file's scope comes from its path alone, prefixes first: a Markdown
file under a supporting prefix is supporting, one under a playground prefix at
any level is playground, any other Markdown file in a book folder is canon, and
everything else is unmapped and never returned. A folder in both lists is
supporting." with:
"A Markdown file's scope comes from its path and from past imports, prefixes
first: a Markdown file under the archive folder is archived, one under a
supporting prefix is supporting, a Markdown file at a converted file's old path is
unmapped under REQ-LIB-8 but flagged in the format map, one under a playground
prefix at any level is playground, any other Markdown file in a book folder is
canon, and everything else is unmapped and never returned. A folder in both lists
is supporting. The archive folder follows the same placement rules and may not
overlap another prefix or a book folder. An archived file keeps the path it had
under the archive folder, and its universe and book are those of that old path. A
DOCX document in the writing store keeps its source's scope: the universe, the
book if there is one, and canon or playground. Its source is the Markdown file it
was converted from or, for one the human starts in the editor, the universe and
book or the playground the human picks (REQ-LIB-8). A playground DOCX follows the
playground rules and is never returned as canon. It stays playground after its
draft universe is made real. In version 1.17 no promotion or mapping change makes
a playground DOCX canon. A later version of this PRD says how one becomes canon."
Replace "Lookups return canon unless the caller asks for playground or
supporting." with "Lookups return canon unless the caller asks for playground,
supporting or archived. Every answer marks an archived file as archived and names
the DOCX that replaced it."
Replace "so an agent can't cite a playground or supporting file as canon" with "so
an agent can't cite a playground, supporting or archived file as canon".
In REQ-LIB-2 only, replace "The Console shows each cite's scope." with "The Console shows each cite's
scope and format, and whether a cited Markdown file was converted."
Version tag, inside the closing parenthesis: "Version 1.15: supporting.)" becomes
"Version 1.15: supporting. Version 1.17: archived scope and DOCX scope.)"
### REQ-LIB-4
Replace "The user commits and pushes with their own tools, today Obsidian with
obsidian-git." with "The user commits and pushes Markdown with their own tools,
today Obsidian with obsidian-git. A converted story file's current text is no
longer in that repository, which keeps only its archived Markdown (REQ-LIB-8)."
Add, after "Bus writes and pushes come in later rows (C2-02, C2-03b).": "A DOCX
story file is saved in the Console's editor, and each accepted save is one commit
by the human in the stack's writing store (REQ-LIB-9). Each writing-store commit
is also pushed to a remote off the stack's host, never by force-push. The remote
is a setting only a human changes, and it starts unset. A repository on the
stack's host never counts as that remote. A missing remote or a failed push never
undoes a save, and the user sees each push that is pending or failed. Only the
stack's host pushes to that remote, and no agent can reach it with any credential.
Agents read the local commit."
Replace "Agents read only the mirror's served commit and never hold a copy of
their own, so they see the user's push within about a minute." with "Agents read
the user's repository only at the mirror's served commit, except a story file
during its import under REQ-LIB-8. They read a DOCX story file only at a committed
version in the writing store, under REQ-LIB-5. They never hold a copy of their
own, so they see the user's Markdown push within about a minute and a DOCX save at
their next lookup after the editor reports it as committed."
Version tag: "and the sync items.)" becomes "and the sync items. Version 1.17,
with Q222 for the remote setting.)"
### REQ-LIB-5
Add, after "Each answer names the commit it read.": "Each answer also names the
file's format and where it lives: its path, or its document id in the writing
store. A DOCX story file is read from its latest committed save, never from
unsaved text. A lookup that starts after the editor reports a save as committed
returns that save or a later one, even while its push to the remote is pending or
has failed, and the answer names the version. Get, search and manuscript read a
DOCX story file as its extracted text with DOCX locators, never as lines. That
text is the file with its tracked changes accepted. An agent reads a story file's
comments, or its text with tracked deletions shown, only after a human turns on
the matching setting. Both settings are off by default, and only a human changes
them. The reader in this version can't read some things a DOCX story file can
hold, such as a web link, a text box, a field or hidden text. The editor warns the
human when an edit adds one, and the human can still save it. Until a later save
takes it out, lookups give agents none of the text of the part that holds it,
whether that is the chapter's body, a note or a comment. The manuscript lookup
follows the book's chapter order across both formats (O2 design D10), and the next
chapter it names is never an archived file. Links out and in resolve across both
formats. The universe lookup returns the format map (REQ-LIB-8)."
Replace "The user sees them in Obsidian's graph and backlinks." with "The user sees
a Markdown file's links in Obsidian's graph and backlinks. A DOCX story file's
wikilinks stay clickable in the Console's editor (O2 design D11), and no graph
shows them yet. The Console's Connections view lists the current links and
backlinks of each story file the user may see, in both formats, with each one's
scope and format."
Version tag: 'and "Connections are always visible".)' becomes 'and "Connections
are always visible". Version 1.17, with Q222 for the two settings and Q239 for
the Connections view.)'
### REQ-LIB-6
Replace "A reply may cite lines of the repository at a commit or pages the agent
fetched, and the broker checks every citation before it stores the message." with
"A reply may cite lines of a Markdown file at a commit, a place in a committed
DOCX story file, or pages the agent fetched, and the broker checks every citation
before it stores the message. A DOCX citation never calls a paragraph a line. It
names the document id, the committed version and a DOCX locator. A citation that
names an archived file as current is refused. The `requireCitation` setting below
applies to Markdown and DOCX citations alike, and so does the rule for a citation
marked as canon."
Add, after "the broker refuses such a write before any git call.": "The writing
store and the archive folder are read-only to a creative agent as well, and the
broker's path check treats the archive folder as story. No agent creates, edits,
deletes, converts, archives or restores a story file in any format, and no agent
writes a DOCX. No creative agent starts or undoes a conversion, and none asks for
one through a tool or a decision it raises, as REQ-LIB-8 says. An agent may still
suggest a conversion to the human in chat. Each creative role's contract states
which files are DOCX and which are Markdown, and cites a DOCX story file by its
locator, never by line."
Version tag, inside the closing parenthesis: "Version 1.15.)" becomes "Version
1.15. Version 1.17.)"
### New REQ-LIB-8
This bullet goes after REQ-LIB-7 and before the "### Installer" heading.
"- **REQ-LIB-8.** Story files become DOCX. DOCX is the standard for chapters and
the other story text in a Case 2 writing project, canon and playground alike. A
story file becomes DOCX through a human's import (below), or when the human
starts it in the editor. From then on DOCX is its only working format, edited in
the Console's editor (REQ-LIB-9). A Markdown story file stays Markdown until its
import. Files in a type folder (REQ-LIB-1), such as character and place notes,
stay Markdown. So do supporting files, agent files, instructions, PRDs and
documentation, including the character sheets, summaries and cross-links agents
keep under REQ-LIB-6.
- A converted story file's current text leaves the user's Gitea-mirrored
repository. Its DOCX lives in the stack's writing store, on the stack's host,
and not in the repository the user pushes or opens in Obsidian. Only its
archived Markdown stays there.
- An import converts one Markdown story file, canon or playground, outside every
type folder (REQ-LIB-1), to a DOCX document in the writing store. It refuses
an unmapped, supporting, archived or type-folder file, and a file that is
already converted. Each import has its own fidelity report, the human's
confirmation of that report, a manifest naming the source file and its commit,
and one commit in the store. The DOCX keeps every wiki link of the source as
written, and a lost link to another library file fails the fidelity report. A
web link becomes plain text that keeps its label and address, and the fidelity
report lists it for the human to confirm. A web link that can't be kept safely
as plain text refuses the import.
- The human can also start a new story file in the editor, in a universe and
book or a playground the human picks. It is a DOCX document in the writing
store from its first commit, with no Markdown copy, and it opens under the
same gate as the import.
- The same import marks the Markdown file converted, naming the document, and
moves it to the archive folder under its old path (REQ-LIB-1, REQ-LIB-2).
The mark and the move are one commit authored by the human in the user's
repository. A new human-only bus step (Vikunja 571) on the C2-02 write
plumbing writes it, and C2-03b pushes it. The move keeps the bytes and the
git history, so it isn't `data.delete`.
- The store commit comes first, and the writing store's remote must confirm it
holds that commit. Then come the archive commit and its push, then publication
to the lookups. Until publication the Markdown at its old path keeps its canon
or playground scope. Agents read that file at the commit the import started
from, even after the mirror's served commit reaches the archive commit or a
later one. No lookup returns the file's archived copy before publication, even
one that asks for archived files. A refusal or failure before the archive push
leaves the Markdown unmoved and no DOCX that a lookup returns. A failure after
the archive push leaves a pending import that the human sees and can resume,
never a claim that nothing moved. An archive push whose result is unknown
counts as a failure after the archive push until the stack reads the result
back from the user's remote.
- Each stage of an import writes a logged event. The import's record on the
stack's host names the source file's path, commit and blob and the new
document id, and each event points to that record.
- After a conversion, a Markdown file at the converted file's old path is
neither canon nor playground. Lookups leave it out, the format map flags it,
and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
canon until its import, unless it sits at a converted file's old path or under
a playground prefix. The Console flags it and offers the import. Nothing
converts it on its own.
- A hybrid's exclusion that covers a converted file's old path also covers its
DOCX document (REQ-LIB-3). An exclusion of a whole book folder also covers
every DOCX document in that book, including one started in the editor. An
exclusion can also name a DOCX document by its id.
- Only a human starts an import or a new story file, from the CLI or a Console
session signed in as that human. Both are human verbs outside the agent action
vocabulary. Neither is added to `ACTIONS` in the business vocabulary or to the
broker's always-gated list, including the import's action, `library.convert`.
No role holds them, no agent can request them, and the hard-wait list in
REQ-WAIT-1 doesn't change. In an organization with several humans, who may
start them is a setting only a human changes, and the default is the owner
only.
- Conversion is one way, with no parallel run. Neither the Console nor any agent
edits a converted story file as Markdown, and no DOCX edit syncs back to
Markdown. The Console's Markdown save refuses every file under the archive
folder. The user's own tools can still change the archived Markdown, and no
such change reaches the DOCX. The old Markdown stays in git history, and
nothing rewrites history. A promotion into a book folder (C2-19) runs the
import for a Markdown story file or is refused. A promotion of a playground
DOCX is refused in version 1.17.
- Agents get a format map: for each mapped folder and story file, its format,
scope and where it lives, archived files, flagged files and pending imports
included.
- The import and new story files ship closed. They open only after four things.
The editor ships. The human has set the writing store's remote, and the stack
has checked that it can push there. Agents can read committed DOCX story files
through the lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent
system that writes the user's files has stopped, which for Case 2 is the
Hermes retirement (#423 step 5). So the import itself never cuts agents off
from a chapter, the chapter's current text never lives only on the stack's
host, and no second system writes the story. The first import of a real user's
books waits for Jason's go.
(Q235, Q236, Q237: P1 to P6. Q222 for the setting. O2 design D10 to D12.
Version 1.17.)"
### New REQ-LIB-9
This bullet goes after REQ-LIB-8.
"- **REQ-LIB-9.** The Console's DOCX editor. The human edits a DOCX story file in
the Console's editor, in a split pane with the agent chat alongside it. The
human can pin an agent chat to a document. The human can select a word,
right-click it, and choose a synonym from a thesaurus. Only a human saves. Saves
happen on their own while the human types (Q226), at intervals only a human
changes, and each accepted save is one commit with the human as author, pushed
as REQ-LIB-4 says. A save made against an older version than the current one is
refused. Agents never write a DOCX. Versioning is the editor's tracked changes
plus one git commit per save. The editor's code is vendored and pinned, with a
hash per file and the licenses that its independent security review accepted. At
runtime the editor loads only files that the stack itself serves, and it
requests nothing from any other host. The editor's frame sends exactly the
content security policy Jason approved in Q234, and the WebUI shell's own header
is unchanged. REQ-PROJ-3's project docs editor is unchanged. (Q195, Q226, Q234,
Q236. O2 design D3, D4, D6, D8, D10, D13, D14, D16 and D22. Version 1.17.)"
### Version 1.17 notes
Added after the 1.16 notes:
"- Version 1.17:
- Jason confirmed the reading 'for now (subject to future change)' (Q237). The
rulings stand until he revises them.
- The 10-27 Case 2 release ships on Markdown as built (P6). The 1.11 line 'The
Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG editor (Q195,
Q196) is an add-on that doesn't gate it.' still holds. Conversion opens after
10-27, and only once REQ-LIB-8's four conditions are met. The editor ships,
the writing store's remote is set and the stack has checked that it can push
there, agents can read and cite committed DOCX story files through the
lookups, and Hermes is retired, which is #423 step 5.
- The 1.12 note says five bus write and Console editor rows, C2-02, C2-03b,
C2-11a, C2-11b and C2-19, don't gate 10-27. Jason's Q226 F42 later moved
C2-02, C2-03b and C2-11b into the 10-27 slice. The Case 2 design brings C2-11a
in with them, because C2-11b needs it. C2-19 stays outside the slice. Mos's
reading of F42 is that C2-02 and C2-03b now gate 10-27, because design 9.2
puts them in 'the smallest slice that lets her start'. C2-11a and C2-11b are
slice rows that still don't gate 10-27, because the editor is the Q195 add-on.
The DOCX change in 1.17 leaves this slice as it is, and C2-11b's Markdown
editor never edits a converted story file.
- Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9). The
1.12 note that the Case 2 design keeps Obsidian now holds only for Markdown
files, including story files not yet imported. A DOCX story file is edited
only in the Console's editor under REQ-LIB-9.
- This replaces two parts of the O2 design at 25c09f19f. The superseded-notice
proposal from Fenton's F1 in D12 is withdrawn, and the
keep-Markdown-at-its-path model of D11 is replaced. Gyro's rewrite of the O2
design for Vikunja 571, landed on `refactor` at b99ca86b9, carries the
details, and where it differs from this text, this text holds:
- the import and archive steps, the mark, and the new bus step;
- links from supporting files to a moved chapter;
- the DOCX reader;
- the user's unpushed or later changes to an archived file or at its old path,
and pushing and closing the file in Obsidian before each import;
- the writing store's remote in W4 section 7.3, which replaces the no-remote
and no-backup rule that W4 section 7.4 held before the rewrite, at
062646487;
- import events counted by a bus restore, and recovery when a bus restore or a
restore of the whole host predates an import;
- manifest order for the manuscript lookup and its next chapter;
- `library.convert` kept out of `ACTIONS`, out of the hard-wait note and out
of the pinned `GATED_ONLY` list in packages/business/src/vocabulary.mjs.
- Agents read a converted story file through the Case 2 lookups under the same
project and universe access as Markdown canon, in any thread or room. W4's
earlier O2-only conversations, per-document grants and per-send sharing don't
gate these lookups. The 571 rewrite replaced them in W4 sections 7.1 and 7.2.
The creative tool list in REQ-LIB-6 doesn't change.
- The six creative role contracts row 213 landed say 'Every item cites the
lines of the user's material it is about'. A build row under 571 changes that
line for DOCX story files (REQ-LIB-6).
- A new Markdown file in a book folder is still canon in 1.17. The 1.17
exception is a file at a converted file's old path, which REQ-LIB-2 makes
unmapped.
- Story files convert whether canon or playground. Type-folder and supporting
files stay Markdown.
- Measured 2026-10-23 at about 01:21Z: the Case 2 user's library repository
tracks 213 files, 203 of them Markdown, and none is a .docx or .doc. It has no
untracked files. The user's .doc files (Q196) are outside it. So the landed
code has no DOCX there to report as unreadable at 10-27.
- Text replaced in 1.17, kept here as in earlier notes:
- Creative library lead, the 1.16 sentence: "The library is the user's own
writing repository."
- REQ-LIB-2, the 1.16 sentences: "A file's scope comes from its path alone,
prefixes first: a Markdown file under a supporting prefix is supporting,
one under a playground prefix at any level is playground, any other
Markdown file in a book folder is canon, and everything else is unmapped
and never returned. A folder in both lists is supporting." "Lookups return
canon unless the caller asks for playground or supporting." "so an agent
can't cite a playground or supporting file as canon" "The Console shows
each cite's scope."
- REQ-LIB-4, the 1.16 sentences: "The user commits and pushes with their own
tools, today Obsidian with obsidian-git." "Agents read only the mirror's
served commit and never hold a copy of their own, so they see the user's
push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
stood at conversion, and the Console's Connections view shows their
current links.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
- REQ-LIB-1 only gains sentences.
- REQ-LIB-3's text and REQ-LIB-7 are unchanged. REQ-LIB-8 adds one form of
exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. A path
exclusion also covers DOCX documents, through a converted file's old path or a
whole book folder. An archived file and a DOCX document keep a universe, and a
book where their source has one, so every lookup still names one universe.
REQ-PROJ-3, the Console editor for a project's spec and docs, is unchanged.
C2-11b opens the library's Markdown files in that same editor.
- In `packages/tasks/requirements.json` the titles of REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 change, and those of REQ-LIB-5 and REQ-LIB-6 stay. REQ-LIB-1 becomes
'A human-set library maps the user's repository onto universes, books, types,
playgrounds, supporting and archive folders'. REQ-LIB-2 becomes 'Scope comes
from the path or a DOCX's source, lookups default to canon, and only canon
files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change. The
bus fast-forwards a read-only mirror every 60 seconds, the one place agents
read Markdown'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes from 85
to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only
one-way import or in the editor; Markdown stays for notes and docs', and
REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one
commit, and the editor code is vendored and pinned'.
- Open points O1 to O4 of draft 1 were ruled by Mos in mos-1367 B1 to B4.
- Sage files the build rows under Vikunja 570 and 571 after the rewrite is
approved. This amendment adds the requirements only."
### Risks
The new lines go at the end of "## Risks and open questions", after the bullet
that begins "Trusted mode lets an agent" and before the "## Milestones" heading.
- "A DOCX file is a zip archive, so git can't show a line diff of a chapter.
Review of a story change goes through the editor and the committed reader,
not `git diff`. (Version 1.17.)"
- "A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
chapter only through its archived Markdown, and no DOCX edit reaches that file's
links. The DOCX's current links show in the Console's editor and its
Connections view, and no graph shows them yet. (Version 1.17.)"
- "Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
confirmation, an untouched original on refusal, and the kept archive.
(Version 1.17.)"
- "A restore of the stack's bus alone from a backup taken before an import drops
the import's logged events, while the archive commit and the DOCX stay. A
restore of the stack's whole host from such a backup also takes the writing
store and the host's import records back to before the import. The archive
commit stays in the user's Gitea repository and the DOCX stays on the writing
store's remote, but the host keeps no record of the import. A bus restore counts
import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)"
## d9 changes (Q239 and Webby round 5)
- J-b yes (Q239, mos-1467), in the five edits mos-1443 R3 set for a yes (Webby round 6 B1). R3.1: REQ-LIB-5's replacement gains the Connections view sentence after "no graph shows them yet.". R3.2: the graph Risks line ends with the editor and the Connections view. R3.3: the A6 line moves from "Not taken" under A6. R3.5: the REQ-LIB-5 note ends with the Connections view, which drops "until a Console view of their links exists" and "Q226 records that view as a later option". R3.4 is Gyro's design edit at 767bb62c7 (writing-editor.md:188-190, :240-244 and :1272, docx-citations.md:66), and it follows in his docs round. Beyond these and N21, nothing else in the PRD text changes.
- N19 and N20 (Webby round 6): J-b's question text is in the past tense, and the line that the import doesn't wait for the view is cited as Mos's R3 default. Mos confirmed it at his J-b check (mos-1470).
- N21 (Webby round 6, Mos yes in mos-1470): REQ-LIB-5's version tag cites Q239 for the Connections view beside Q222 for the two settings.
- N15: the D7 landing line gives R8's wording beside the impact map's quote.
- N16: the d7 changes line notes that R8 replaces "no agent credential reaches it" in d8.
- N17: the REQ-LIB-2 Replace quote and the REQ-LIB-8 old-path bullet are rewrapped under 82 columns. Their words are unchanged.
## d8 changes (Mos's d8 fix list, check wf_63bba879-180)
- R1: REQ-LIB-4's tail covers DOCX saves, which agents see at their next lookup after the editor reports them as committed. The notes quote the whole 1.16 sentence. Source: nit/jason/F10.
- R2: REQ-LIB-4's new title says the mirror is "the one place agents read Markdown". Source: decisions.n14.
- R3: J-b asks whether 1.17 requires the Console Connections view, with Mos's recommendation of yes. Sources: mos1372/F9, jason/F2, anchors/F5.
- R4: C2-02 and C2-03b gate 10-27, C2-11a and C2-11b are slice rows that don't, and "F42 wins" is gone. New J-k. Sources: anchors/F6, jason/F5.
- R5: where the landed 571 design differs, this text holds. Sources: anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6, G16's design follow-on.
- R6: `library.convert` stays out of `ACTIONS`, and REQ-WAIT-1's hard-wait list doesn't change. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
- R7: a repository on the stack's host never counts as the writing store's remote, and Item 2 says so. Sources: mos1372/F2, adversarial/F1, gyro571/F2.
- R8: "no agent credential reaches it" becomes "no agent can reach it with any credential". Source: Fenton's B1 under mos-1379.
- R9: no playground DOCX becomes canon in 1.17, and a promotion of one is refused. New J-i. Sources: mos1372/F8, adversarial/F2.
- R10: REQ-LIB-9 restates Q195's pin and right-click thesaurus, and its cite gains D4 and D22. Source: critic/C4.
- R11: REQ-LIB-9's license bar is the licenses its independent security review accepted. New J-l. Source: critic/C3.
- R12: no text for D16's narrow-window tabs. Mos records that reading in STATE. Sources: jason/F1, second verifier vote.
- R13: no lookup returns a file's archived copy before publication. Source: adversarial/F5 part 3.
- R14: a restore of the whole host from a backup taken before an import is a fourth recovery case. Source: adversarial/F6.
- M1: REQ-LIB-4's writing-store sentences follow the later bus rows sentence and add the off-host remote rules and DOCX read timing. The note quotes both 1.16 sentences. Sources: mos1372/F2, adversarial/F1, gyro571/F2, anchors/F4, nit/jason/F10, adversarial/F5 part 1, Fenton B1.
- M2: REQ-LIB-8's order bullet adds the remote's confirmation, the old-path read rule and "archive push". Sources: mos1372/F2 and the other remote findings, mos1372/F7, adversarial/F5 parts 2 to 4.
- M3: REQ-LIB-8's gate bullet has four conditions, adding the remote. Sources: the remote findings, critic/C1 and C2 edit 2.
- M4: the 1.17 note on when conversion opens restates the four conditions and drops W4b. Sources: jason/F5, the remote findings.
- M5: Item 2 rewritten with the remote rules. Sources: the remote findings.
- M6: the 1.12 gating note rewritten. Sources: anchors/F6, jason/F5.
- M7: REQ-LIB-9 gains the split pane, the pin, the thesaurus, the license bar and the runtime load rule, and cites more of the design. Sources: jason/F1, critic/C4, critic/C3, nit/anchors/F12, nit/gyro571/F9.
- M8: REQ-LIB-2, the one-way bullet and the C1 note keep a playground DOCX out of canon in 1.17. Sources: mos1372/F8, adversarial/F2.
- M9: REQ-LIB-5 says what the reader can't read, and the import turns web links into plain text. New J-j. Sources: critic/C1, critic/C2.
- M10: an exclusion of a whole book folder covers that book's DOCX documents. Source: adversarial/F3, with B5.
- M11: the human-verbs sentence and the 571 list item name `library.convert` and REQ-WAIT-1. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
- M12: J-b rewritten. Sources: mos1372/F9, jason/F2, anchors/F5, gyro571/F3, critic/C6.
- M13: J-d puts the agent read model to Jason as his call. Source: mos1372/F1.
- M14: Status moves to version 1.17 and names REQ-LIB-9. Sources: anchors/F2, mos1372/F4, critic/C7.
- M15: the 1.17 source line quotes Q235 in full (mos-1446) and the REQ-LIB-9 sources, at b99ca86b9. Sources: critic/C5, anchors/F9, jason/F8 pin part, nit/gyro571/F8, critic/C6 pin part.
- M16: the 571 list lead names the two replaced design parts and the landed design. Sources: anchors/F9, anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6.
- Landing swap: Gyro's docs round for R5, R6 and R14 landed as b99ca86b9 (Fenton fenton-0015), so M15 and M16 cite b99ca86b9 in place of 767bb62c7, as the d8 fix list's header says. J-b keeps 767bb62c7 for its line numbers.
- M17: W4 section numbers in the remote item and the lookups note. Sources: gyro571/F5, nit/adversarial/F11.
- M18: the D7 landing line. Sources: gyro571/F5, the remote findings.
- M19: a logged event for each import stage, in REQ-LIB-8 and A2. Source: gyro571/F7.
- M20: the Risks line on restores adds the whole-host case. Sources: adversarial/F6, gyro571/F7.
- M21: the 571 restore item adds the whole-host case. Source: adversarial/F6.
- M22: the graph Risks line and the REQ-LIB-5 note name a Console view. Sources: anchors/F5, jason/F2.
- M23: the d6 B3 line drops "F42 wins". Source: anchors/F6.
- M24: new Markdown in a converted book, with the old-path and playground exceptions. Source: mos1372/F6.
- M25: J-c's last sentence is Mos's reading. Source: mos1372/F10.
- M26: J-h rewritten with the D13 defaults. Source: jason/F3.
- M27: Item 4 rewritten. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
- M28: the "Not taken" note on unmarked folders. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
- M29: the import refuses an unmapped file. Sources: gyro571/F4, decisions.unmarked_folders.
- M30: placement notes for REQ-LIB-8, REQ-LIB-9 and the Risks lines. Sources: anchors/F3, nit/anchors/F14.
- M31: REQ-LIB-4's title and the D11 landing line. Source: decisions.n14.
- M32: the Q236 note limits the 1.12 Obsidian note to Markdown files. Source: critic/C8.
- M33: the exclusion note adds path exclusions of DOCX documents. Source: anchors/F7.
- M34: A9 moves from "Mine" to the impact-map list. Source: decisions.exclusion_by_id.
- M35: the D5 landing line. Sources: anchors/F10, mos1372/F5, nit/adversarial/F12.
- M36: the D9 and D14 landing lines. Source: mos1372/F1.
- M37: the O1 and C3 notes add the off-host remote and its confirmation. Sources: the remote findings.
- M38: the REQ-PROJ-3 note. Source: nit/anchors/F11.
- M39: REQ-LIB-5's freshness sentence keys on the editor reporting a save as committed. Source: nit/adversarial/F9.
- M40: REQ-LIB-6 bars agent requests for a conversion and allows a suggestion in chat. Source: nit/jason/F9.
- M41: REQ-LIB-2's opening and old-path rule, and the "Mine" note. Source: nit/adversarial/F10.
- M42: REQ-LIB-8's lead sentence on Markdown story files. Source: nit/mos1372/F12.
- M43: REQ-LIB-6's citation sentence. Source: nit/anchors/F13.
- M44: REQ-LIB-5's version tag uses plain quotes. Source: nit/anchors/F15.
- M45: reference codes B5, D12, D10 and B4 name their source. Source: nit/mos1372/F11.
- Files the row would own: four files, with the id count going to 87. Sources: anchors/F1, adversarial/F4, decisions.n14 F4.
## d7 changes (mos-1379, Fenton's 571 B2)
- REQ-LIB-5 gains Mos's three sentences after the extracted-text sentence. The text agents read has tracked changes accepted. Comments, and text with tracked deletions shown, each wait for a setting only a human turns on, off by default. Its version tag cites Q222 for the two settings, which is the only text here beyond Mos's.
- J-d gains Mos's sentence on comments and tracked deletions.
- Fenton's B1 is design only. d6's "no agent credential reaches it" holds (R8 replaces it in d8), and the shared helper stays #431.
## d6 changes (Webby round 3, webby-0207)
- B1: titles for REQ-LIB-8 and REQ-LIB-9 in the requirements.json note and the file list. REQ-LIB-9 takes Webby's title. REQ-LIB-8 keeps mine, because Webby's ("...one-way import that archives the Markdown under its old path") leaves out D2's story files started in the editor.
- B2: REQ-LIB-2 makes a Markdown file at a converted file's old path unmapped, ahead of the playground and canon rules, so a playground conversion is covered too. REQ-LIB-8's old-path bullet says "neither canon nor playground".
- B3: the slice note names Q226 F42's three rows (C2-02, C2-03b, C2-11b). C2-11a joined the slice with C2-11b, because C2-11b needs it. Both are slice rows that don't gate 10-27, as design 9.2, case2-rows.md:1655 and the impact map's A57 say. The 'F42 wins' sentence is gone from the note in d8, so no queue change follows from it.
- N10: the cite-scope replacement is scoped to REQ-LIB-2. The 1.14 note's copy stays.
- N11: the notes say REQ-LIB-8 adds the by-id exclusion form REQ-LIB-3 doesn't have.
- N12: J-h marks the autosave intervals for Jason.
- N13: J-b names REQ-LIB-5's "Connections are always visible" tag and Q226's "another way to handle the visual brain map".
- N14, not taken: REQ-LIB-4's title stays internal F3's text, as mos-1372 D11 ruled. Webby's alternative, "...and agents read Markdown from it alone" (120 chars), is Mos's call.
## Where each mos-1372 item landed
- D1: REQ-LIB-2's DOCX sentences, the REQ-LIB-8 lead and import bullet, C1 above, and the notes. "Playground files stay Markdown in 1.17" is gone.
- D2: REQ-LIB-2's "for one the human starts in the editor" and the REQ-LIB-8 new-story-file bullet. The universe-and-book note is now true.
- D3: the REQ-LIB-8 order bullet. This also settles N8.
- D4: REQ-LIB-8's one-way bullet, which carries the archive refusal and consequence F2's text, and the 571 list item.
- D5: it landed in REQ-LIB-2's "a Markdown file at a converted file's old path is unmapped" in Webby's B2 form, and in the REQ-LIB-8 old-path bullet.
- D6: REQ-LIB-5's freshness sentences.
- D7 and mos-1375: REQ-LIB-4's remote sentences, the remote step in REQ-LIB-8's order bullet, the fourth opening condition and the 1.17 note on when conversion opens, plus the 571 list item on W4 section 7.3, which replaces the no-remote rule of the old W4 section 7.4. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it", which R8 words in d8 as "no agent can reach it with any credential".
- D8: the REQ-LIB-5 replacement, the import bullet's wiki-link sentence, the Risks line, the note quoting the old sentence, and "REQ-LIB-1 only gains sentences".
- D9: the 1.17 note that agents read a converted story file through the Case 2 lookups, from the d4 check's consistency B2, and J-d, which puts the read model to Jason as his call.
- D10: the d4 check's B5 sentence, in REQ-LIB-8.
- D11: internal F3's clause in the REQ-LIB-4 section, and in the notes the title as Mos amended it for N14.
- D12: the whole 1.16 sentence replaced, and quoted in full in the notes.
- D13: consequence F1's text, in REQ-LIB-4 and REQ-LIB-8's first bullet.
- D14: the row text from the d4 check's consistency B1, in the mark-and-move bullet and C3 above.
## What d5 adds beyond mos-1372 (for Webby and Mos to check)
From the impact map, folded as mos-1368 item 1 asked:
- A1: the Console shows a cite's format and converted state (REQ-LIB-2).
- A2: the logged events and the several-humans setting, default owner only, under Q222.
- A3: the import is outside the vocabulary, the hard-wait list is unchanged, and the archive move isn't `data.delete`. The C6 code site is in the 571 list.
- A4: the section lead and REQ-LIB-1's writing-store sentence.
- A6: format and location in every answer, extracted text with DOCX locators, links across formats.
- A6's named chapter-links view. D8 took B8's "no graph shows them yet", and J-b put the view to Jason. His O2 (Q239) takes it in d9 as REQ-LIB-5's Connections view (mos-1443 R3).
- A7: read-only extends to the store and the archive, no agent action on a story file in any format, no agent DOCX write, and role contracts state the formats. C1's role contract line is a 571 build item.
- A8: the DOCX cite shape, and a cite naming an archived file as current is refused.
- A9: a hybrid sees a source's DOCX canon through REQ-LIB-2's source scope, and REQ-LIB-8 lets an exclusion name a DOCX document by its id. The id form lets the human exclude a single DOCX started in the editor, which has no old path. An archived file isn't canon, so no hybrid sees it.
- A10: REQ-LIB-9, so the count is 87, not 86.
- A11: the Hermes gate, from item 3.
- A12: the C2-19 promotion sentence.
- A13: the broker's path check treats the archive folder as story.
- A14 and C5: two Risks lines.
- A15: the flagged-new-file bullet. It sits beside the note from the d4 check's B4, "a new Markdown file in a book folder is still canon". The file stays canon and is offered for import.
- A16: three titles change and two stay.
- C2: manifest order for the manuscript lookup.
- F3: the C2-02 gating note.
- F4: Mos's measurement.
Mine, beyond either source:
- REQ-LIB-2 opens "A Markdown file's scope comes from its path and from past imports". DOCX scope comes from the source, so "a file's" would be false.
- REQ-LIB-9's autosave sentence. It is Mos's reading of F41's "instantly committed", with human-set intervals. The exact intervals stay in the design.
Not taken:
- Impact map item 4's "files in unmarked folders are eligible one at a time", for folders outside every book folder and playground only. Their files are unmapped under REQ-LIB-2, so a DOCX made from one would have no universe or book to keep. The human maps such a folder into a book folder or a playground first, under REQ-LIB-1. A file in an unmarked folder inside a book folder or a playground is canon or playground, and the import takes it one file at a time.
## Marked for Jason (additions in the text he sees)
- J-a. Whether "We will not run the systems in parallel" applies per chapter or as one switch per book. His call. As written, it applies per chapter.
- J-b. A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the chapter only through its archived Markdown, and no DOCX edit reaches that file's links. REQ-LIB-5 still cites two TARGET lines, "Visualization of data via Obsidian or equivalent is required" and "Connections are always visible". Mos's reading was that 1.17 as d8 wrote it met neither line for converted chapters. Their current links showed to the user only as clickable text inside the editor, and no graph or list showed them. Q226 leaves room for "another way to handle the visual brain map". The O2 design that landed for Vikunja 571 at 767bb62c7 held a Console Connections view and its tests as later work that waited for this answer. His call was whether 1.17 requires that view. As d8 was written, it did not. Mos recommended that it does, because impact map A6 says "Recommended: name the view" and TARGET says "Connections are always visible". Jason answered O2 (Q239): 1.17 requires the view. d9 writes it into REQ-LIB-5, its note and the Risks line, in the text of mos-1443 R3. The view comes with the conversion work after 10-27, and the 10-27 Case 2 release stays on Markdown (Q235 P6, Q237). That the import does not wait for the view is Mos's default in mos-1443 R3. Q239 doesn't address it, and Mos confirmed it at his J-b check (mos-1470).
- J-c (mos-1374): "The store pushes to a remote (D7), and the Case 2 VM is in the nightly PBS backup. For a fixed restore point before her first real import, a manual backup of VM 1012 with PBS protect runs first. That step is reversible and needs no reboot." Mos's reading is that this sits inside the P5 go step, so it needs no new question.
- J-d. How agents read a converted chapter, D9. His call. As written, an agent reads it through the ordinary Case 2 lookups in any thread or room, under the same project and universe access as Markdown canon. This is Mos's reading of F41 and REQ-LIB-7. The other choice is the model in the earlier O2 design, W4 sections 7.1 and 7.2. Under it, an agent reads a converted chapter only in a new writing conversation that holds one human and one agent, only after the business owner grants that agent access to that document, and by default only what the human attaches to each message. Comments and tracked deletions stay hidden from agents until a human turns on a setting for each. Markdown has neither, so the default keeps agents at the access Markdown gives them. Those two sentences are Mos's ruling mos-1379. A yes to the text as written settles it.
- J-e. Versioning means the editor's tracked changes plus one git commit per save.
- J-f. A converted chapter can be edited only in the Console while the stack is reachable. Obsidian no longer edits it offline.
- J-g. The import takes Markdown only. Importing .doc files stays a separate task.
- J-h. Autosave under REQ-LIB-9. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." Mos's reading, recorded with that ruling, is "Each save is a commit at once, and agents read it at their next lookup." At the O2 design's D13 defaults, a save commits at most 2 seconds after a pause in typing or 15 seconds into steady typing, with no human action needed. Mos's reading in mos-1359 counts that delay as "instantly committed". Only a human changes the intervals. The PRD names no values, and the defaults stay in the design. Whether a delay of seconds fits his word "instantly" is his call, so this is marked for him.
- J-i. In 1.17 a playground story that is DOCX can't become canon. That covers a playground Markdown file the human imports, a story the human starts in a playground in the editor, and the DOCX of a draft universe after it is made real. Today Melanie promotes a playground file by moving it into a book folder in Obsidian, and a DOCX in the writing store can't be moved that way. She can start a new story in the book and paste the text in, but it gets a new document id and the playground copy keeps the history. A later version adds DOCX promotion with C2-19, which comes after the slice.
- J-j. Web links, text boxes and fields in a converted chapter. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." The reader in this version can't read a web link, a text box, a field or hidden text. As written, the import turns each web link into plain text that keeps its label and address, and the human confirms that in the fidelity report. A web link that can't be kept safely as plain text refuses the import. If a later edit adds one of these, the save keeps it and the editor warns the human. Agents then get none of that part's text until a save takes it out.
- J-k. The 1.12 note Jason approved says bus writes, C2-02 and C2-03b, don't gate 10-27. His Q226 "F42: ok" moved them into the 10-27 slice, and design 9.2 counts them in the smallest slice that lets Melanie start. Mos reads that as making them gate 10-27. The editor rows C2-11a and C2-11b still don't gate it, per his Q195 words "we need to get it moving as an add-on".
- J-l. REQ-LIB-9 does not use REQ-PROJ-3's "permissive license" bar. It takes the licenses that the editor's independent security review accepted, because that review cleared fonts under the SIL Open Font License and the Ubuntu Font License, which not every reader counts as permissive. Mos's reading.
Marked as changes beyond P1 to P6, from the impact map (Q17, item 5):
- A1 (archived scope and canon cites)
- A2 (the conversion requirement, REQ-LIB-8)
- A3 (outside the vocabulary, hard-wait list unchanged)
- A6 (lookups across formats)
- A7 (agent bounds)
- A8 (DOCX cites)
- Item 2 and mos-1375: each writing-store commit is pushed to a remote off the stack's host. Only a human sets it, it starts unset, and a repository on the stack's host doesn't count. Only the stack's host pushes to it, and no agent can reach it with any credential. Import and new story files stay closed until the remote is set and checked, and each import's archive push waits until the remote holds the DOCX commit. This keeps "Git is used for all changes -- local and remote" true for DOCX chapters.
- Item 3 (the Hermes gate)
- Item 4. Canon and playground story files convert one file at a time, including files in unmarked folders inside a book folder or a playground. Type-folder and supporting files stay Markdown. A story file outside every book folder and playground has no universe or book, so it converts only after a human maps its folder into a book folder or a playground. The mapping step is Mos's reading of P1 and Q30.
The F4 measurement goes with them.
## Files the row would own
The id count goes from 85 to 87. 1.16 owned three files because its count stayed at 85. Like 1.13, the last version that added ids, the row owns four files:
- `docs/prd/mosaic-stack.md`
- `packages/tasks/requirements.json`: the version 1.17, the two new ids REQ-LIB-8 and REQ-LIB-9 with their titles, and the changed titles of REQ-LIB-1, REQ-LIB-2 and REQ-LIB-4. The new ids go after REQ-LIB-7 and before REQ-INST-1, in the same order as the PRD's bullets.
- `packages/tasks/tests/requirements.test.mjs`: the version pin 1.17, the id count of 87, and the duplicate-id message, which goes from `requirements[85].id: REQ-ROLE-1 twice` to `requirements[87].id: REQ-ROLE-1 twice`.
- `packages/bus/tests/requirements.test.mjs`: the list length, which goes from 85 to 87.
The gate would match 1.16's:
1. A reviewer approves the text.
2. Jason says yes to the exact text through Mos.
3. Both `requirements.test.mjs` files, in `packages/tasks/tests` and `packages/bus/tests`, and every `scripts/test-*.sh` that reads the PRD or `requirements.json` are green on Sage's gate.
4. Mos gets the landing commit.
+747
View File
@@ -0,0 +1,747 @@
--- DRAFT-d7.md 2026-10-22 20:44:39.909606789 -0500
+++ DRAFT.md 2026-10-23 06:16:54.370223847 -0500
@@ -1,12 +1,12 @@
# PRD 1.17 draft: Case 2 story files are DOCX (Q235 to Q237, P1 to P6)
-Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 7. It is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
+Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 8. It is d7 plus Mos's d8 fix list mos-1443 (see "d8 changes"). d7 is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
- Mos's d4 check ruling mos-1372 (D1 to D14; D15 was dropped).
- mos-1374, which replaces J-c.
- The impact map mos-1368 (jason-master-restart d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`: A1 to A16, critic items C1 to C6, items 2 to 7).
- Mos's F4 measurement.
-Review order (B5): Webby, then Mos, then Jason. Base: PRD 1.16, `docs/prd/mosaic-stack.md` blob a6db9b4a at 482e865ac.
+Review order (mos-1367 B5): Webby, then Mos, then Jason. Base: PRD 1.16, `docs/prd/mosaic-stack.md` blob a6db9b4a at 482e865ac.
1.16 lands first (row 291), and this text is a new version on top of it. Nothing here is built or queued. The impact map's queue changes (A24, A57 to A59) go through `scripts/mosaic queue` after 1.17 is approved (C9).
Sources, verbatim in Mos's DECISIONS:
@@ -19,11 +19,12 @@
These stand as ruled. d5 changes only what mos-1372 changed.
-- **O1. Where a converted chapter lives.** The DOCX lives in the O2 writing store
- (D12), and the archived Markdown stays in the user's repository under a
+- **O1. Where a converted chapter lives.** The DOCX lives in the writing store of
+ O2 design D12, and the archived Markdown stays in the user's repository under a
human-marked archive folder. Ruled yes in B1, on the condition that the text
says plainly that a converted chapter leaves the Gitea-mirrored repository and
- lives in the stack's writing store. mos-1372 D7 adds a remote for the store.
+ lives in the stack's writing store. mos-1372 D7 and mos-1375 add a remote for
+ the store, off the stack's host.
- **O2. The unit of conversion.** One story file per import, refusing a file
that is already converted. Ruled yes in B2: each file gets its own fidelity
report, human confirmation, manifest and commit.
@@ -40,15 +41,16 @@
universe, book, and canon or playground. A playground DOCX follows the
playground rules and is never returned as canon. The import accepts canon and
playground story files. Type-folder files and supporting files stay Markdown
- (P4). This also settles the impact map's Q18 (item 4) without a narrowing for
- Jason.
+ (P4). This also settles the impact map's Q18, item 4. It leaves one narrowing
+ for Jason, J-i. A playground DOCX can't become canon in 1.17.
- **C2 stands.** The move keeps the file's path under the archive folder:
`book1/ch3.md` goes to `<archive>/book1/ch3.md`. An archived file's universe
and book are those of its old path.
- **C3 is replaced by D3 and D14.** The archive commit is written by a new
- human-only bus step (Vikunja 571) on the C2-02 write plumbing and pushed
- through C2-03b. The order is the store commit, then the archive commit and its
- push, then publication to the lookups.
+ human-only bus step (Vikunja 571) on the C2-02 write plumbing and pushed through
+ C2-03b. The order is the store commit and the remote's confirmation that it
+ holds that commit, then the archive commit and its push, then publication to the
+ lookups.
- **N4 stands.** REQ-LIB-8 says "a real user's books". The PRD names no user
anywhere in Case 2.
@@ -56,19 +58,33 @@
### Status and sources
-Status gains, after the 1.16 sentence: "Version 1.17 records Jason's rulings Q235
-to Q237 of 2026-10-23: Case 2 story files are DOCX, Markdown stays for agent files
-and documentation, and a human-started import converts a Markdown story file one
-way. It changes what Case 2 requirements allow, so Mos puts the text to Jason as
-written, and it lands only after his yes (mos-1366, Vikunja 570)."
+In the Status bullet's first line, "- Status: approved, version 1.16." becomes
+"- Status: approved, version 1.17." Status gains, after the two 1.16 sentences at
+the end of the bullet: "Version 1.17 records Jason's rulings Q235 to Q237 of
+2026-10-23: Case 2 story files are DOCX, Markdown stays for agent files and
+documentation, and a human-started import converts a Markdown story file one way.
+It also adds REQ-LIB-9, the Console's DOCX editor, from Jason's rulings Q195, Q226
+and Q234. It changes what Case 2 requirements allow, so Mos puts the text to Jason
+as written, and it lands only after his yes (mos-1366, Vikunja 570)."
New source line, above the 1.16 one: "Version 1.17 source: Jason's Q235, answering
-Q36, his Q236 'For creative writing chapters, we need to utilize docx as the
-standard. Markdown is acceptable for agent files, instructions, PRDs,
-documentation, etc. The creative writing files should be docx for familiarity for
-the user.', and his Q237 'Q38: It is correct for now (subject to future change)',
-confirming Mos's reading P1 to P6. The storage follows the O2 design D10 and D12,
-25c09f19f, as rewritten under Vikunja 571."
+Q36: 'How about we see a markdown file, we convert the markdown file to docx, then
+archive or mark the markdown file as converted. The new standard for files in the
+creating writing use case is to use docx to handle versioning and WYSIWYG. We
+eliminate the potential for confusion based on the files. Agents must understand
+which files are what format and where they live. The system must allow for import
+and conversion. We will not run the systems in parallel. There will be an
+irreversible conversion of the files.' His Q236 says: 'For creative writing
+chapters, we need to utilize docx as the standard. Markdown is acceptable for
+agent files, instructions, PRDs, documentation, etc. The creative writing files
+should be docx for familiarity for the user.' His Q237 says 'Q38: It is correct
+for now (subject to future change)', which confirms Mos's reading P1 to P6.
+REQ-LIB-9 rests on his Q195 request for 'a full WYSIWYG editor within Mosaic
+Stack', his Q226 answer 'F41: We want to move editing into the WYSIWYG in the
+WebUI. All changes are instantly committed and available to the agents.' and his
+Q234 answer 'Q37: yes' to the exact security header for the editor's frame. The
+storage follows the O2 design D10 to D12 as rewritten under Vikunja 571, at
+b99ca86b9 on `refactor`."
### Creative library, section lead
@@ -92,20 +108,22 @@
is playground, any other Markdown file in a book folder is canon, and everything
else is unmapped and never returned. A folder in both lists is supporting." with:
-"A Markdown file's scope comes from its path alone, prefixes first: a Markdown
-file under the archive folder is archived, one under a supporting prefix is
-supporting, a Markdown file at a converted file's old path is unmapped
-(REQ-LIB-8), one under a playground prefix at any level is playground, any other
-Markdown file in a book folder is canon, and everything else is unmapped and never
-returned. A folder in
-both lists is supporting. The archive folder follows the same placement rules and
-may not overlap another prefix or a book folder. An archived file keeps the path
-it had under the archive folder, and its universe and book are those of that old
-path. A DOCX document in the writing store keeps its source's scope: the
-universe, the book if there is one, and canon or playground. Its source is the
-Markdown file it was converted from or, for one the human starts in the editor,
-the universe and book or the playground the human picks (REQ-LIB-8). A playground
-DOCX follows the playground rules and is never returned as canon."
+"A Markdown file's scope comes from its path and from past imports, prefixes
+first: a Markdown file under the archive folder is archived, one under a
+supporting prefix is supporting, a Markdown file at a converted file's old path is
+unmapped under REQ-LIB-8 but flagged in the format map, one under a playground
+prefix at any level is playground, any other Markdown file in a book folder is
+canon, and everything else is unmapped and never returned. A folder in both lists
+is supporting. The archive folder follows the same placement rules and may not
+overlap another prefix or a book folder. An archived file keeps the path it had
+under the archive folder, and its universe and book are those of that old path. A
+DOCX document in the writing store keeps its source's scope: the universe, the
+book if there is one, and canon or playground. Its source is the Markdown file it
+was converted from or, for one the human starts in the editor, the universe and
+book or the playground the human picks (REQ-LIB-8). A playground DOCX follows the
+playground rules and is never returned as canon. It stays playground after its
+draft universe is made real. In version 1.17 no promotion or mapping change makes
+a playground DOCX canon. A later version of this PRD says how one becomes canon."
Replace "Lookups return canon unless the caller asks for playground or
supporting." with "Lookups return canon unless the caller asks for playground,
@@ -126,89 +144,110 @@
Replace "The user commits and pushes with their own tools, today Obsidian with
obsidian-git." with "The user commits and pushes Markdown with their own tools,
today Obsidian with obsidian-git. A converted story file's current text is no
-longer in that repository, which keeps only its archived Markdown (REQ-LIB-8). A
-DOCX story file is saved in the Console's editor, and each accepted save is one
-commit by the human in the stack's writing store (REQ-LIB-9). Each writing-store
-commit is also pushed to a remote the human sets, never by force-push, and the
-user sees a push that is pending or failed. Only the stack's host pushes to that
-remote, and no agent credential reaches it. Agents read the local commit."
-
-Replace "Agents read only the mirror's served commit and never hold a copy of their
-own" with "Agents read the user's repository only at the mirror's served commit,
-read a DOCX story file only at a committed version in the writing store
-(REQ-LIB-5), and never hold a copy of their own". The rest of that sentence, ", so
-they see the user's push within about a minute.", is unchanged.
+longer in that repository, which keeps only its archived Markdown (REQ-LIB-8)."
-Version tag: "and the sync items.)" becomes "and the sync items. Version 1.17.)"
+Add, after "Bus writes and pushes come in later rows (C2-02, C2-03b).": "A DOCX
+story file is saved in the Console's editor, and each accepted save is one commit
+by the human in the stack's writing store (REQ-LIB-9). Each writing-store commit
+is also pushed to a remote off the stack's host, never by force-push. The remote
+is a setting only a human changes, and it starts unset. A repository on the
+stack's host never counts as that remote. A missing remote or a failed push never
+undoes a save, and the user sees each push that is pending or failed. Only the
+stack's host pushes to that remote, and no agent can reach it with any credential.
+Agents read the local commit."
+
+Replace "Agents read only the mirror's served commit and never hold a copy of
+their own, so they see the user's push within about a minute." with "Agents read
+the user's repository only at the mirror's served commit, except a story file
+during its import under REQ-LIB-8. They read a DOCX story file only at a committed
+version in the writing store, under REQ-LIB-5. They never hold a copy of their
+own, so they see the user's Markdown push within about a minute and a DOCX save at
+their next lookup after the editor reports it as committed."
+
+Version tag: "and the sync items.)" becomes "and the sync items. Version 1.17,
+with Q222 for the remote setting.)"
### REQ-LIB-5
Add, after "Each answer names the commit it read.": "Each answer also names the
file's format and where it lives: its path, or its document id in the writing
store. A DOCX story file is read from its latest committed save, never from
-unsaved text. A lookup that starts after a save is acknowledged returns that save
-or a later one, and the answer names the version. Get, search and manuscript read
-a DOCX story file as its extracted text with DOCX locators, never as lines. That
+unsaved text. A lookup that starts after the editor reports a save as committed
+returns that save or a later one, even while its push to the remote is pending or
+has failed, and the answer names the version. Get, search and manuscript read a
+DOCX story file as its extracted text with DOCX locators, never as lines. That
text is the file with its tracked changes accepted. An agent reads a story file's
comments, or its text with tracked deletions shown, only after a human turns on
the matching setting. Both settings are off by default, and only a human changes
-them. The manuscript lookup follows the book's chapter order across both formats (O2 design
-D10), and the next chapter it names is never an archived file. Links out and in
-resolve across both formats. The universe lookup returns the format map
-(REQ-LIB-8)."
+them. The reader in this version can't read some things a DOCX story file can
+hold, such as a web link, a text box, a field or hidden text. The editor warns the
+human when an edit adds one, and the human can still save it. Until a later save
+takes it out, lookups give agents none of the text of the part that holds it,
+whether that is the chapter's body, a note or a comment. The manuscript lookup
+follows the book's chapter order across both formats (O2 design D10), and the next
+chapter it names is never an archived file. Links out and in resolve across both
+formats. The universe lookup returns the format map (REQ-LIB-8)."
Replace "The user sees them in Obsidian's graph and backlinks." with "The user sees
a Markdown file's links in Obsidian's graph and backlinks. A DOCX story file's
wikilinks stay clickable in the Console's editor (O2 design D11), and no graph
shows them yet."
-Version tag: "and \"Connections are always visible\".)" becomes "and \"Connections
-are always visible\". Version 1.17, with Q222 for the two settings.)"
+Version tag: 'and "Connections are always visible".)' becomes 'and "Connections
+are always visible". Version 1.17, with Q222 for the two settings.)'
### REQ-LIB-6
Replace "A reply may cite lines of the repository at a commit or pages the agent
fetched, and the broker checks every citation before it stores the message." with
-"A reply may cite lines of a Markdown file at a commit, a place in a committed DOCX
-story file, or pages the agent fetched, and the broker checks every citation
+"A reply may cite lines of a Markdown file at a commit, a place in a committed
+DOCX story file, or pages the agent fetched, and the broker checks every citation
before it stores the message. A DOCX citation never calls a paragraph a line. It
names the document id, the committed version and a DOCX locator. A citation that
-names an archived file as current is refused. `requireCitation` and a citation
-marked as canon work the same way for both kinds."
+names an archived file as current is refused. The `requireCitation` setting below
+applies to Markdown and DOCX citations alike, and so does the rule for a citation
+marked as canon."
Add, after "the broker refuses such a write before any git call.": "The writing
store and the archive folder are read-only to a creative agent as well, and the
broker's path check treats the archive folder as story. No agent creates, edits,
deletes, converts, archives or restores a story file in any format, and no agent
-writes a DOCX. No creative agent starts, requests or undoes a conversion
-(REQ-LIB-8). Each creative role's contract states which files are DOCX and which
-are Markdown, and cites a DOCX story file by its locator, never by line."
+writes a DOCX. No creative agent starts or undoes a conversion, and none asks for
+one through a tool or a decision it raises, as REQ-LIB-8 says. An agent may still
+suggest a conversion to the human in chat. Each creative role's contract states
+which files are DOCX and which are Markdown, and cites a DOCX story file by its
+locator, never by line."
Version tag, inside the closing parenthesis: "Version 1.15.)" becomes "Version
1.15. Version 1.17.)"
### New REQ-LIB-8
+This bullet goes after REQ-LIB-7 and before the "### Installer" heading.
+
"- **REQ-LIB-8.** Story files become DOCX. DOCX is the standard for chapters and
the other story text in a Case 2 writing project, canon and playground alike. A
story file becomes DOCX through a human's import (below), or when the human
- starts it in the editor. From then on DOCX is its only working format, edited
- in the Console's editor (REQ-LIB-9). Until its import a story file stays
- Markdown. Files in a type folder (REQ-LIB-1), such as character and place
- notes, stay Markdown. So do supporting files, agent files, instructions, PRDs
- and documentation, including the character sheets, summaries and cross-links
- agents keep under REQ-LIB-6.
+ starts it in the editor. From then on DOCX is its only working format, edited in
+ the Console's editor (REQ-LIB-9). A Markdown story file stays Markdown until its
+ import. Files in a type folder (REQ-LIB-1), such as character and place notes,
+ stay Markdown. So do supporting files, agent files, instructions, PRDs and
+ documentation, including the character sheets, summaries and cross-links agents
+ keep under REQ-LIB-6.
- A converted story file's current text leaves the user's Gitea-mirrored
repository. Its DOCX lives in the stack's writing store, on the stack's host,
and not in the repository the user pushes or opens in Obsidian. Only its
archived Markdown stays there.
- - An import converts one Markdown story file, canon or playground, outside
- every type folder (REQ-LIB-1), to a DOCX document in the writing store. It
- refuses a supporting, archived or type-folder file, and a file that is
+ - An import converts one Markdown story file, canon or playground, outside every
+ type folder (REQ-LIB-1), to a DOCX document in the writing store. It refuses
+ an unmapped, supporting, archived or type-folder file, and a file that is
already converted. Each import has its own fidelity report, the human's
- confirmation of that report, a manifest naming the source file and its
- commit, and one commit in the store. The DOCX keeps every wiki link of the
- source as written, and a lost link fails the fidelity report.
+ confirmation of that report, a manifest naming the source file and its commit,
+ and one commit in the store. The DOCX keeps every wiki link of the source as
+ written, and a lost link to another library file fails the fidelity report. A
+ web link becomes plain text that keeps its label and address, and the fidelity
+ report lists it for the human to confirm. A web link that can't be kept safely
+ as plain text refuses the import.
- The human can also start a new story file in the editor, in a universe and
book or a playground the human picks. It is a DOCX document in the writing
store from its first commit, with no Markdown copy, and it opens under the
@@ -219,61 +258,81 @@
repository. A new human-only bus step (Vikunja 571) on the C2-02 write
plumbing writes it, and C2-03b pushes it. The move keeps the bytes and the
git history, so it isn't `data.delete`.
- - The store commit comes first, then the archive commit and its push, then
- publication to the lookups. Until publication the Markdown stays canon at
- its old path, and agents read the source at the commit the import started
- from. A refusal or failure before the push leaves the Markdown unmoved and
- no DOCX that a lookup returns. A failure after the push leaves a pending
- import that the human sees and can resume, never a claim that nothing moved.
- - Each import writes one logged event naming the source file's path, commit
- and blob and the new document id.
+ - The store commit comes first, and the writing store's remote must confirm it
+ holds that commit. Then come the archive commit and its push, then publication
+ to the lookups. Until publication the Markdown at its old path keeps its canon
+ or playground scope. Agents read that file at the commit the import started
+ from, even after the mirror's served commit reaches the archive commit or a
+ later one. No lookup returns the file's archived copy before publication, even
+ one that asks for archived files. A refusal or failure before the archive push
+ leaves the Markdown unmoved and no DOCX that a lookup returns. A failure after
+ the archive push leaves a pending import that the human sees and can resume,
+ never a claim that nothing moved. An archive push whose result is unknown
+ counts as a failure after the archive push until the stack reads the result
+ back from the user's remote.
+ - Each stage of an import writes a logged event. The import's record on the
+ stack's host names the source file's path, commit and blob and the new
+ document id, and each event points to that record.
- After a conversion, a Markdown file at the converted file's old path is neither
canon nor playground. Lookups leave it out, the format map flags it, and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
- canon until its import. The Console flags it and offers the import. Nothing
+ canon until its import, unless it sits at a converted file's old path or under
+ a playground prefix. The Console flags it and offers the import. Nothing
converts it on its own.
- A hybrid's exclusion that covers a converted file's old path also covers its
- DOCX document (REQ-LIB-3). An exclusion can also name a DOCX document by its
- id.
+ DOCX document (REQ-LIB-3). An exclusion of a whole book folder also covers
+ every DOCX document in that book, including one started in the editor. An
+ exclusion can also name a DOCX document by its id.
- Only a human starts an import or a new story file, from the CLI or a Console
- session signed in as that human. Both are human verbs outside the agent
- action vocabulary, so no role holds them, no agent can request them, and
- REQ-WAIT-2's list is unchanged. In an organization with several humans, who
- may start them is a setting only a human changes, and the default is the
- owner only.
- - Conversion is one way, with no parallel run. Neither the Console nor any
- agent edits a converted story file as Markdown, and no DOCX edit syncs back
- to Markdown. The Console's Markdown save refuses every file under the archive
+ session signed in as that human. Both are human verbs outside the agent action
+ vocabulary. Neither is added to `ACTIONS` in the business vocabulary or to the
+ broker's always-gated list, including the import's action, `library.convert`.
+ No role holds them, no agent can request them, and the hard-wait list in
+ REQ-WAIT-1 doesn't change. In an organization with several humans, who may
+ start them is a setting only a human changes, and the default is the owner
+ only.
+ - Conversion is one way, with no parallel run. Neither the Console nor any agent
+ edits a converted story file as Markdown, and no DOCX edit syncs back to
+ Markdown. The Console's Markdown save refuses every file under the archive
folder. The user's own tools can still change the archived Markdown, and no
such change reaches the DOCX. The old Markdown stays in git history, and
nothing rewrites history. A promotion into a book folder (C2-19) runs the
- import for a Markdown story file or is refused.
+ import for a Markdown story file or is refused. A promotion of a playground
+ DOCX is refused in version 1.17.
- Agents get a format map: for each mapped folder and story file, its format,
scope and where it lives, archived files, flagged files and pending imports
included.
- - The import and new story files ship closed. They open only after three things.
- The editor ships. Agents can read committed DOCX story files through the
- lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent system that
- writes the user's files has stopped, which for Case 2 is the Hermes
- retirement (#423 step 5). So agents never lose the current chapter, and no
- second system writes the story. The first import of a real user's books
- waits for Jason's go.
+ - The import and new story files ship closed. They open only after four things.
+ The editor ships. The human has set the writing store's remote, and the stack
+ has checked that it can push there. Agents can read committed DOCX story files
+ through the lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent
+ system that writes the user's files has stopped, which for Case 2 is the
+ Hermes retirement (#423 step 5). So the import itself never cuts agents off
+ from a chapter, the chapter's current text never lives only on the stack's
+ host, and no second system writes the story. The first import of a real user's
+ books waits for Jason's go.
(Q235, Q236, Q237: P1 to P6. Q222 for the setting. O2 design D10 to D12.
Version 1.17.)"
### New REQ-LIB-9
+This bullet goes after REQ-LIB-8.
+
"- **REQ-LIB-9.** The Console's DOCX editor. The human edits a DOCX story file in
- the Console's editor, on a page of its own. Only a human saves. Saves happen
- on their own while the human types (Q226), at intervals only a human changes,
- and each accepted save is one commit with the human as author, pushed as
- REQ-LIB-4 says. A save made against an older version than the current one is
+ the Console's editor, in a split pane with the agent chat alongside it. The
+ human can pin an agent chat to a document. The human can select a word,
+ right-click it, and choose a synonym from a thesaurus. Only a human saves. Saves
+ happen on their own while the human types (Q226), at intervals only a human
+ changes, and each accepted save is one commit with the human as author, pushed
+ as REQ-LIB-4 says. A save made against an older version than the current one is
refused. Agents never write a DOCX. Versioning is the editor's tracked changes
- plus one git commit per save. The editor's code is vendored and pinned, with
- its license and a hash per file, and nothing loads at runtime. The editor's
- frame sends exactly the content security policy Jason approved in Q234, and
- the WebUI shell's own header is unchanged. REQ-PROJ-3's project docs editor
- is unchanged. (Q195, Q226, Q234, Q236. O2 design D10 and D14. Version 1.17.)"
+ plus one git commit per save. The editor's code is vendored and pinned, with a
+ hash per file and the licenses that its independent security review accepted. At
+ runtime the editor loads only files that the stack itself serves, and it
+ requests nothing from any other host. The editor's frame sends exactly the
+ content security policy Jason approved in Q234, and the WebUI shell's own header
+ is unchanged. REQ-PROJ-3's project docs editor is unchanged. (Q195, Q226, Q234,
+ Q236. O2 design D3, D4, D6, D8, D10, D13, D14, D16 and D22. Version 1.17.)"
### Version 1.17 notes
@@ -285,40 +344,52 @@
- The 10-27 Case 2 release ships on Markdown as built (P6). The 1.11 line 'The
Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG editor (Q195,
Q196) is an add-on that doesn't gate it.' still holds. Conversion opens after
- 10-27, after W4b, and after Hermes is retired (#423 step 5).
- - The 1.12 note says bus writes and the Console editor (C2-02, C2-03b, C2-11a,
- C2-11b, C2-19) don't gate 10-27. Jason's Q226 F42 later moved three of them,
- C2-02, C2-03b and C2-11b, into the 10-27 slice, and that later ruling holds.
- C2-11a and C2-19 still don't gate 10-27. Where a plan or queue record names
- a different set, F42 wins, and the record is corrected through its own
- process.
- 1.17 changes neither, and C2-11b's Markdown editor never edits a converted
- story file.
- - Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9).
- - This replaces the superseded-notice proposal in O2 design D12 (Fenton F1),
- which is withdrawn, and the keep-Markdown-at-its-path model of D11. Gyro's
- rewrite of the O2 design (Vikunja 571) carries the details:
+ 10-27, and only once REQ-LIB-8's four conditions are met. The editor ships,
+ the writing store's remote is set and the stack has checked that it can push
+ there, agents can read and cite committed DOCX story files through the
+ lookups, and Hermes is retired, which is #423 step 5.
+ - The 1.12 note says five bus write and Console editor rows, C2-02, C2-03b,
+ C2-11a, C2-11b and C2-19, don't gate 10-27. Jason's Q226 F42 later moved
+ C2-02, C2-03b and C2-11b into the 10-27 slice. The Case 2 design brings C2-11a
+ in with them, because C2-11b needs it. C2-19 stays outside the slice. Mos's
+ reading of F42 is that C2-02 and C2-03b now gate 10-27, because design 9.2
+ puts them in 'the smallest slice that lets her start'. C2-11a and C2-11b are
+ slice rows that still don't gate 10-27, because the editor is the Q195 add-on.
+ The DOCX change in 1.17 leaves this slice as it is, and C2-11b's Markdown
+ editor never edits a converted story file.
+ - Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9). The
+ 1.12 note that the Case 2 design keeps Obsidian now holds only for Markdown
+ files, including story files not yet imported. A DOCX story file is edited
+ only in the Console's editor under REQ-LIB-9.
+ - This replaces two parts of the O2 design at 25c09f19f. The superseded-notice
+ proposal from Fenton's F1 in D12 is withdrawn, and the
+ keep-Markdown-at-its-path model of D11 is replaced. Gyro's rewrite of the O2
+ design for Vikunja 571, landed on `refactor` at b99ca86b9, carries the
+ details, and where it differs from this text, this text holds:
- the import and archive steps, the mark, and the new bus step;
- links from supporting files to a moved chapter;
- the DOCX reader;
- the user's unpushed or later changes to an archived file or at its old path,
and pushing and closing the file in Obsidian before each import;
- - the writing store's remote, with W4 7.4's no-remote and no-backup rule taken
- out;
- - import events counted by a stack restore, and recovery when a restore
- predates an import;
+ - the writing store's remote in W4 section 7.3, which replaces the no-remote
+ and no-backup rule that W4 section 7.4 held before the rewrite, at
+ 062646487;
+ - import events counted by a bus restore, and recovery when a bus restore or a
+ restore of the whole host predates an import;
- manifest order for the manuscript lookup and its next chapter;
- - the import verb kept out of `ACTIONS` (packages/business/src/vocabulary.mjs,
- the hard-wait note and the pinned `GATED_ONLY` list).
+ - `library.convert` kept out of `ACTIONS`, out of the hard-wait note and out
+ of the pinned `GATED_ONLY` list in packages/business/src/vocabulary.mjs.
- Agents read a converted story file through the Case 2 lookups under the same
project and universe access as Markdown canon, in any thread or room. W4's
- O2-only conversations, per-document grants and per-send sharing (W4 7.1, 7.2)
- don't gate these lookups, and the 571 rewrite brings W4 into line. The
- creative tool list in REQ-LIB-6 doesn't change.
+ earlier O2-only conversations, per-document grants and per-send sharing don't
+ gate these lookups. The 571 rewrite replaced them in W4 sections 7.1 and 7.2.
+ The creative tool list in REQ-LIB-6 doesn't change.
- The six creative role contracts row 213 landed say 'Every item cites the
lines of the user's material it is about'. A build row under 571 changes that
line for DOCX story files (REQ-LIB-6).
- - A new Markdown file in a book folder is still canon in 1.17.
+ - A new Markdown file in a book folder is still canon in 1.17. The 1.17
+ exception is a file at a converted file's old path, which REQ-LIB-2 makes
+ unmapped.
- Story files convert whether canon or playground. Type-folder and supporting
files stay Markdown.
- Measured 2026-10-23 at about 01:21Z: the Case 2 user's library repository
@@ -336,52 +407,130 @@
canon unless the caller asks for playground or supporting." "so an agent
can't cite a playground or supporting file as canon" "The Console shows
each cite's scope."
- - REQ-LIB-4, the 1.16 sentence: "The user commits and pushes with their own
- tools, today Obsidian with obsidian-git." and the clause "Agents read only
- the mirror's served commit and never hold a copy of their own".
+ - REQ-LIB-4, the 1.16 sentences: "The user commits and pushes with their own
+ tools, today Obsidian with obsidian-git." "Agents read only the mirror's
+ served commit and never hold a copy of their own, so they see the user's
+ push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
- stood at conversion, until a WebUI view exists (Q226).
+ stood at conversion, until a Console view of their links exists. Q226
+ records that view as a later option.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
- REQ-LIB-1 only gains sentences.
- REQ-LIB-3's text and REQ-LIB-7 are unchanged. REQ-LIB-8 adds one form of
- exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. An archived file and a DOCX document
- keep a universe, and a book where their source has one, so every lookup still
- names one universe. REQ-PROJ-3, the project docs editor, is not Case 2 and is
- unchanged.
+ exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. A path
+ exclusion also covers DOCX documents, through a converted file's old path or a
+ whole book folder. An archived file and a DOCX document keep a universe, and a
+ book where their source has one, so every lookup still names one universe.
+ REQ-PROJ-3, the Console editor for a project's spec and docs, is unchanged.
+ C2-11b opens the library's Markdown files in that same editor.
- In `packages/tasks/requirements.json` the titles of REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 change, and those of REQ-LIB-5 and REQ-LIB-6 stay. REQ-LIB-1 becomes
'A human-set library maps the user's repository onto universes, books, types,
playgrounds, supporting and archive folders'. REQ-LIB-2 becomes 'Scope comes
from the path or a DOCX's source, lookups default to canon, and only canon
- files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change;
- the bus fast-forwards a read-only mirror every 60 seconds and agents read
- Markdown only there'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes
- from 85 to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only one-way import or in the editor; Markdown stays for notes and docs', and REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one commit, and the editor code is vendored and pinned'.
+ files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change. The
+ bus fast-forwards a read-only mirror every 60 seconds, the one place agents
+ read Markdown'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes from 85
+ to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only
+ one-way import or in the editor; Markdown stays for notes and docs', and
+ REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one
+ commit, and the editor code is vendored and pinned'.
- Open points O1 to O4 of draft 1 were ruled by Mos in mos-1367 B1 to B4.
- Sage files the build rows under Vikunja 570 and 571 after the rewrite is
approved. This amendment adds the requirements only."
### Risks
-New lines:
+The new lines go at the end of "## Risks and open questions", after the bullet
+that begins "Trusted mode lets an agent" and before the "## Milestones" heading.
- "A DOCX file is a zip archive, so git can't show a line diff of a chapter.
Review of a story change goes through the editor and the committed reader,
not `git diff`. (Version 1.17.)"
-- "Converted chapters leave Obsidian's graph. Their links show only in the
- editor until a graph view exists. (Version 1.17.)"
+- "A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
+ chapter only through its archived Markdown, and no DOCX edit reaches that file's
+ links. The DOCX's current links show only in the Console's editor until a
+ Console view of them exists. (Version 1.17.)"
- "Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
confirmation, an untouched original on refusal, and the kept archive.
(Version 1.17.)"
-- "A stack restore from a backup taken before an import drops the import's
- logged event, while the archive commit and the DOCX stay. Restore counts
- import events, and the import's recovery covers this case (Vikunja 571).
+- "A restore of the stack's bus alone from a backup taken before an import drops
+ the import's logged events, while the archive commit and the DOCX stay. A
+ restore of the stack's whole host from such a backup also takes the writing
+ store and the host's import records back to before the import. The archive
+ commit stays in the user's Gitea repository and the DOCX stays on the writing
+ store's remote, but the host keeps no record of the import. A bus restore counts
+ import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)"
+## d8 changes (Mos's d8 fix list, check wf_63bba879-180)
+
+- R1: REQ-LIB-4's tail covers DOCX saves, which agents see at their next lookup after the editor reports them as committed. The notes quote the whole 1.16 sentence. Source: nit/jason/F10.
+- R2: REQ-LIB-4's new title says the mirror is "the one place agents read Markdown". Source: decisions.n14.
+- R3: J-b asks whether 1.17 requires the Console Connections view, with Mos's recommendation of yes. Sources: mos1372/F9, jason/F2, anchors/F5.
+- R4: C2-02 and C2-03b gate 10-27, C2-11a and C2-11b are slice rows that don't, and "F42 wins" is gone. New J-k. Sources: anchors/F6, jason/F5.
+- R5: where the landed 571 design differs, this text holds. Sources: anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6, G16's design follow-on.
+- R6: `library.convert` stays out of `ACTIONS`, and REQ-WAIT-1's hard-wait list doesn't change. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
+- R7: a repository on the stack's host never counts as the writing store's remote, and Item 2 says so. Sources: mos1372/F2, adversarial/F1, gyro571/F2.
+- R8: "no agent credential reaches it" becomes "no agent can reach it with any credential". Source: Fenton's B1 under mos-1379.
+- R9: no playground DOCX becomes canon in 1.17, and a promotion of one is refused. New J-i. Sources: mos1372/F8, adversarial/F2.
+- R10: REQ-LIB-9 restates Q195's pin and right-click thesaurus, and its cite gains D4 and D22. Source: critic/C4.
+- R11: REQ-LIB-9's license bar is the licenses its independent security review accepted. New J-l. Source: critic/C3.
+- R12: no text for D16's narrow-window tabs. Mos records that reading in STATE. Sources: jason/F1, second verifier vote.
+- R13: no lookup returns a file's archived copy before publication. Source: adversarial/F5 part 3.
+- R14: a restore of the whole host from a backup taken before an import is a fourth recovery case. Source: adversarial/F6.
+- M1: REQ-LIB-4's writing-store sentences follow the later bus rows sentence and add the off-host remote rules and DOCX read timing. The note quotes both 1.16 sentences. Sources: mos1372/F2, adversarial/F1, gyro571/F2, anchors/F4, nit/jason/F10, adversarial/F5 part 1, Fenton B1.
+- M2: REQ-LIB-8's order bullet adds the remote's confirmation, the old-path read rule and "archive push". Sources: mos1372/F2 and the other remote findings, mos1372/F7, adversarial/F5 parts 2 to 4.
+- M3: REQ-LIB-8's gate bullet has four conditions, adding the remote. Sources: the remote findings, critic/C1 and C2 edit 2.
+- M4: the 1.17 note on when conversion opens restates the four conditions and drops W4b. Sources: jason/F5, the remote findings.
+- M5: Item 2 rewritten with the remote rules. Sources: the remote findings.
+- M6: the 1.12 gating note rewritten. Sources: anchors/F6, jason/F5.
+- M7: REQ-LIB-9 gains the split pane, the pin, the thesaurus, the license bar and the runtime load rule, and cites more of the design. Sources: jason/F1, critic/C4, critic/C3, nit/anchors/F12, nit/gyro571/F9.
+- M8: REQ-LIB-2, the one-way bullet and the C1 note keep a playground DOCX out of canon in 1.17. Sources: mos1372/F8, adversarial/F2.
+- M9: REQ-LIB-5 says what the reader can't read, and the import turns web links into plain text. New J-j. Sources: critic/C1, critic/C2.
+- M10: an exclusion of a whole book folder covers that book's DOCX documents. Source: adversarial/F3, with B5.
+- M11: the human-verbs sentence and the 571 list item name `library.convert` and REQ-WAIT-1. Sources: gyro571/F1, adversarial/F8 item 3, critic/C6.
+- M12: J-b rewritten. Sources: mos1372/F9, jason/F2, anchors/F5, gyro571/F3, critic/C6.
+- M13: J-d puts the agent read model to Jason as his call. Source: mos1372/F1.
+- M14: Status moves to version 1.17 and names REQ-LIB-9. Sources: anchors/F2, mos1372/F4, critic/C7.
+- M15: the 1.17 source line quotes Q235 in full (mos-1446) and the REQ-LIB-9 sources, at b99ca86b9. Sources: critic/C5, anchors/F9, jason/F8 pin part, nit/gyro571/F8, critic/C6 pin part.
+- M16: the 571 list lead names the two replaced design parts and the landed design. Sources: anchors/F9, anchors/F8, adversarial/F8 item 1, jason/F8, critic/C6.
+- Landing swap: Gyro's docs round for R5, R6 and R14 landed as b99ca86b9 (Fenton fenton-0015), so M15 and M16 cite b99ca86b9 in place of 767bb62c7, as the d8 fix list's header says. J-b keeps 767bb62c7 for its line numbers.
+- M17: W4 section numbers in the remote item and the lookups note. Sources: gyro571/F5, nit/adversarial/F11.
+- M18: the D7 landing line. Sources: gyro571/F5, the remote findings.
+- M19: a logged event for each import stage, in REQ-LIB-8 and A2. Source: gyro571/F7.
+- M20: the Risks line on restores adds the whole-host case. Sources: adversarial/F6, gyro571/F7.
+- M21: the 571 restore item adds the whole-host case. Source: adversarial/F6.
+- M22: the graph Risks line and the REQ-LIB-5 note name a Console view. Sources: anchors/F5, jason/F2.
+- M23: the d6 B3 line drops "F42 wins". Source: anchors/F6.
+- M24: new Markdown in a converted book, with the old-path and playground exceptions. Source: mos1372/F6.
+- M25: J-c's last sentence is Mos's reading. Source: mos1372/F10.
+- M26: J-h rewritten with the D13 defaults. Source: jason/F3.
+- M27: Item 4 rewritten. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
+- M28: the "Not taken" note on unmarked folders. Sources: jason/F4, gyro571/F4, decisions.unmarked_folders.
+- M29: the import refuses an unmapped file. Sources: gyro571/F4, decisions.unmarked_folders.
+- M30: placement notes for REQ-LIB-8, REQ-LIB-9 and the Risks lines. Sources: anchors/F3, nit/anchors/F14.
+- M31: REQ-LIB-4's title and the D11 landing line. Source: decisions.n14.
+- M32: the Q236 note limits the 1.12 Obsidian note to Markdown files. Source: critic/C8.
+- M33: the exclusion note adds path exclusions of DOCX documents. Source: anchors/F7.
+- M34: A9 moves from "Mine" to the impact-map list. Source: decisions.exclusion_by_id.
+- M35: the D5 landing line. Sources: anchors/F10, mos1372/F5, nit/adversarial/F12.
+- M36: the D9 and D14 landing lines. Source: mos1372/F1.
+- M37: the O1 and C3 notes add the off-host remote and its confirmation. Sources: the remote findings.
+- M38: the REQ-PROJ-3 note. Source: nit/anchors/F11.
+- M39: REQ-LIB-5's freshness sentence keys on the editor reporting a save as committed. Source: nit/adversarial/F9.
+- M40: REQ-LIB-6 bars agent requests for a conversion and allows a suggestion in chat. Source: nit/jason/F9.
+- M41: REQ-LIB-2's opening and old-path rule, and the "Mine" note. Source: nit/adversarial/F10.
+- M42: REQ-LIB-8's lead sentence on Markdown story files. Source: nit/mos1372/F12.
+- M43: REQ-LIB-6's citation sentence. Source: nit/anchors/F13.
+- M44: REQ-LIB-5's version tag uses plain quotes. Source: nit/anchors/F15.
+- M45: reference codes B5, D12, D10 and B4 name their source. Source: nit/mos1372/F11.
+- Files the row would own: four files, with the id count going to 87. Sources: anchors/F1, adversarial/F4, decisions.n14 F4.
+
## d7 changes (mos-1379, Fenton's 571 B2)
- REQ-LIB-5 gains Mos's three sentences after the extracted-text sentence. The text agents read has tracked changes accepted. Comments, and text with tracked deletions shown, each wait for a setting only a human turns on, off by default. Its version tag cites Q222 for the two settings, which is the only text here beyond Mos's.
@@ -392,7 +541,7 @@
- B1: titles for REQ-LIB-8 and REQ-LIB-9 in the requirements.json note and the file list. REQ-LIB-9 takes Webby's title. REQ-LIB-8 keeps mine, because Webby's ("...one-way import that archives the Markdown under its old path") leaves out D2's story files started in the editor.
- B2: REQ-LIB-2 makes a Markdown file at a converted file's old path unmapped, ahead of the playground and canon rules, so a playground conversion is covered too. REQ-LIB-8's old-path bullet says "neither canon nor playground".
-- B3: the slice note names Q226 F42's three rows (C2-02, C2-03b, C2-11b). C2-11a doesn't gate 10-27. F42 wins where case2-rows.md:1284-1285 or the impact map's A57 says otherwise.
+- B3: the slice note names Q226 F42's three rows (C2-02, C2-03b, C2-11b). C2-11a joined the slice with C2-11b, because C2-11b needs it. Both are slice rows that don't gate 10-27, as design 9.2, case2-rows.md:1655 and the impact map's A57 say. The 'F42 wins' sentence is gone from the note in d8, so no queue change follows from it.
- N10: the cite-scope replacement is scoped to REQ-LIB-2. The 1.14 note's copy stays.
- N11: the notes say REQ-LIB-8 adds the by-id exclusion form REQ-LIB-3 doesn't have.
- N12: J-h marks the autosave intervals for Jason.
@@ -405,57 +554,61 @@
- D2: REQ-LIB-2's "for one the human starts in the editor" and the REQ-LIB-8 new-story-file bullet. The universe-and-book note is now true.
- D3: the REQ-LIB-8 order bullet. This also settles N8.
- D4: REQ-LIB-8's one-way bullet, which carries the archive refusal and consequence F2's text, and the 571 list item.
-- D5: REQ-LIB-2's "unless its path is a converted file's old path" and the REQ-LIB-8 old-path bullet.
+- D5: it landed in REQ-LIB-2's "a Markdown file at a converted file's old path is unmapped" in Webby's B2 form, and in the REQ-LIB-8 old-path bullet.
- D6: REQ-LIB-5's freshness sentences.
-- D7: REQ-LIB-4's remote sentences, plus the 571 list item that takes out W4 7.4's rule. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it".
+- D7 and mos-1375: REQ-LIB-4's remote sentences, the remote step in REQ-LIB-8's order bullet, the fourth opening condition and the 1.17 note on when conversion opens, plus the 571 list item on W4 section 7.3, which replaces the no-remote rule of the old W4 section 7.4. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it".
- D8: the REQ-LIB-5 replacement, the import bullet's wiki-link sentence, the Risks line, the note quoting the old sentence, and "REQ-LIB-1 only gains sentences".
-- D9: B2's note.
-- D10: B5's sentence, in REQ-LIB-8.
-- D11: internal F3's clause and title, in the REQ-LIB-4 section and the notes.
+- D9: the 1.17 note that agents read a converted story file through the Case 2 lookups, from the d4 check's consistency B2, and J-d, which puts the read model to Jason as his call.
+- D10: the d4 check's B5 sentence, in REQ-LIB-8.
+- D11: internal F3's clause in the REQ-LIB-4 section, and in the notes the title as Mos amended it for N14.
- D12: the whole 1.16 sentence replaced, and quoted in full in the notes.
- D13: consequence F1's text, in REQ-LIB-4 and REQ-LIB-8's first bullet.
-- D14: B1's row text, in the mark-and-move bullet and C3 above.
+- D14: the row text from the d4 check's consistency B1, in the mark-and-move bullet and C3 above.
## What d5 adds beyond mos-1372 (for Webby and Mos to check)
From the impact map, folded as mos-1368 item 1 asked:
- A1: the Console shows a cite's format and converted state (REQ-LIB-2).
-- A2: the logged event and the several-humans setting, default owner only (Q222).
+- A2: the logged events and the several-humans setting, default owner only, under Q222.
- A3: the import is outside the vocabulary, the hard-wait list is unchanged, and the archive move isn't `data.delete`. The C6 code site is in the 571 list.
- A4: the section lead and REQ-LIB-1's writing-store sentence.
- A6: format and location in every answer, extracted text with DOCX locators, links across formats.
- A7: read-only extends to the store and the archive, no agent action on a story file in any format, no agent DOCX write, and role contracts state the formats. C1's role contract line is a 571 build item.
- A8: the DOCX cite shape, and a cite naming an archived file as current is refused.
+- A9: a hybrid sees a source's DOCX canon through REQ-LIB-2's source scope, and REQ-LIB-8 lets an exclusion name a DOCX document by its id. The id form lets the human exclude a single DOCX started in the editor, which has no old path. An archived file isn't canon, so no hybrid sees it.
- A10: REQ-LIB-9, so the count is 87, not 86.
- A11: the Hermes gate, from item 3.
- A12: the C2-19 promotion sentence.
- A13: the broker's path check treats the archive folder as story.
- A14 and C5: two Risks lines.
-- A15: the flagged-new-file bullet. It sits beside B4's "a new Markdown file in a book folder is still canon" note: the file stays canon and is offered for import.
+- A15: the flagged-new-file bullet. It sits beside the note from the d4 check's B4, "a new Markdown file in a book folder is still canon". The file stays canon and is offered for import.
- A16: three titles change and two stay.
- C2: manifest order for the manuscript lookup.
- F3: the C2-02 gating note.
- F4: Mos's measurement.
Mine, beyond either source:
-- REQ-LIB-2 opens "A Markdown file's scope comes from its path alone". DOCX scope comes from the source, so "a file's" would be false.
-- REQ-LIB-8's exclusion-by-id sentence. A DOCX started in the editor has no old path, so without it a hybrid couldn't exclude one.
+- REQ-LIB-2 opens "A Markdown file's scope comes from its path and from past imports". DOCX scope comes from the source, so "a file's" would be false.
- REQ-LIB-9's autosave sentence. It is Mos's reading of F41's "instantly committed", with human-set intervals. The exact intervals stay in the design.
Not taken:
-- Impact map item 4's "files in unmarked folders are eligible one at a time". D1 names canon and playground story files only, and an unmapped file has no scope for a DOCX to keep. If Mos wants unmarked folders in, the human would map them first.
+- Impact map item 4's "files in unmarked folders are eligible one at a time", for folders outside every book folder and playground only. Their files are unmapped under REQ-LIB-2, so a DOCX made from one would have no universe or book to keep. The human maps such a folder into a book folder or a playground first, under REQ-LIB-1. A file in an unmarked folder inside a book folder or a playground is canon or playground, and the import takes it one file at a time.
- A6's named chapter-links view. D8 takes B8's "no graph shows them yet", and J-b puts the view to Jason.
## Marked for Jason (additions in the text he sees)
- J-a. Whether "We will not run the systems in parallel" applies per chapter or as one switch per book. His call. As written, it applies per chapter.
-- J-b. Converted chapters leave Obsidian's graph, and 1.17 has no other graph. REQ-LIB-5 still carries its "Connections are always visible" tag, and Q226 leaves room for "another way to handle the visual brain map". As written, that tag holds for Markdown files and for links inside the editor, and a graph of DOCX links waits for that other way.
-- J-c (mos-1374): "The store pushes to a remote (D7), and the Case 2 VM is in the nightly PBS backup. For a fixed restore point before her first real import, a manual backup of VM 1012 with PBS protect runs first. That step is reversible and needs no reboot." This belongs to the P5 go step, so no new question.
-- J-d. The agent read model in D9. A converted chapter is read through the ordinary Case 2 lookups in any thread or room, not through W4's per-document grants. Mos's reading of F41 and REQ-LIB-7. Comments and tracked deletions stay hidden from agents until a human turns on a setting for each. Markdown has neither, so the default keeps agents at the access Markdown gives them (mos-1379).
+- J-b. A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the chapter only through its archived Markdown, and no DOCX edit reaches that file's links. REQ-LIB-5 still cites two TARGET lines, "Visualization of data via Obsidian or equivalent is required" and "Connections are always visible". Mos's reading is that 1.17 as written meets neither line for converted chapters. Their current links show to the user only as clickable text inside the editor, and no graph or list shows them. Q226 leaves room for "another way to handle the visual brain map". The O2 design that landed for Vikunja 571 at 767bb62c7 holds a Console Connections view and its tests as later work that waits for this answer. His call whether 1.17 requires that view. As written, it does not. Mos recommends that it does, because impact map A6 says "Recommended: name the view" and TARGET says "Connections are always visible".
+- J-c (mos-1374): "The store pushes to a remote (D7), and the Case 2 VM is in the nightly PBS backup. For a fixed restore point before her first real import, a manual backup of VM 1012 with PBS protect runs first. That step is reversible and needs no reboot." Mos's reading is that this sits inside the P5 go step, so it needs no new question.
+- J-d. How agents read a converted chapter, D9. His call. As written, an agent reads it through the ordinary Case 2 lookups in any thread or room, under the same project and universe access as Markdown canon. This is Mos's reading of F41 and REQ-LIB-7. The other choice is the model in the earlier O2 design, W4 sections 7.1 and 7.2. Under it, an agent reads a converted chapter only in a new writing conversation that holds one human and one agent, only after the business owner grants that agent access to that document, and by default only what the human attaches to each message. Comments and tracked deletions stay hidden from agents until a human turns on a setting for each. Markdown has neither, so the default keeps agents at the access Markdown gives them. Those two sentences are Mos's ruling mos-1379. A yes to the text as written settles it.
- J-e. Versioning means the editor's tracked changes plus one git commit per save.
- J-f. A converted chapter can be edited only in the Console while the stack is reachable. Obsidian no longer edits it offline.
- J-g. The import takes Markdown only. Importing .doc files stays a separate task.
-- J-h. Autosave (REQ-LIB-9) runs at intervals only a human changes. F41 says saves are "instantly committed". The intervals are text beyond that, so they are marked for him. The exact values stay in the design.
+- J-h. Autosave under REQ-LIB-9. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." Mos's reading, recorded with that ruling, is "Each save is a commit at once, and agents read it at their next lookup." At the O2 design's D13 defaults, a save commits at most 2 seconds after a pause in typing or 15 seconds into steady typing, with no human action needed. Mos's reading in mos-1359 counts that delay as "instantly committed". Only a human changes the intervals. The PRD names no values, and the defaults stay in the design. Whether a delay of seconds fits his word "instantly" is his call, so this is marked for him.
+- J-i. In 1.17 a playground story that is DOCX can't become canon. That covers a playground Markdown file the human imports, a story the human starts in a playground in the editor, and the DOCX of a draft universe after it is made real. Today Melanie promotes a playground file by moving it into a book folder in Obsidian, and a DOCX in the writing store can't be moved that way. She can start a new story in the book and paste the text in, but it gets a new document id and the playground copy keeps the history. A later version adds DOCX promotion with C2-19, which comes after the slice.
+- J-j. Web links, text boxes and fields in a converted chapter. Jason's Q226 F41 says "All changes are instantly committed and available to the agents." The reader in this version can't read a web link, a text box, a field or hidden text. As written, the import turns each web link into plain text that keeps its label and address, and the human confirms that in the fidelity report. A web link that can't be kept safely as plain text refuses the import. If a later edit adds one of these, the save keeps it and the editor warns the human. Agents then get none of that part's text until a save takes it out.
+- J-k. The 1.12 note Jason approved says bus writes, C2-02 and C2-03b, don't gate 10-27. His Q226 "F42: ok" moved them into the 10-27 slice, and design 9.2 counts them in the smallest slice that lets Melanie start. Mos reads that as making them gate 10-27. The editor rows C2-11a and C2-11b still don't gate it, per his Q195 words "we need to get it moving as an add-on".
+- J-l. REQ-LIB-9 does not use REQ-PROJ-3's "permissive license" bar. It takes the licenses that the editor's independent security review accepted, because that review cleared fonts under the SIL Open Font License and the Ubuntu Font License, which not every reader counts as permissive. Mos's reading.
Marked as changes beyond P1 to P6, from the impact map (Q17, item 5):
- A1 (archived scope and canon cites)
@@ -464,21 +617,22 @@
- A6 (lookups across formats)
- A7 (agent bounds)
- A8 (DOCX cites)
-- Item 2 (the remote only the stack's host pushes)
+- Item 2 and mos-1375: each writing-store commit is pushed to a remote off the stack's host. Only a human sets it, it starts unset, and a repository on the stack's host doesn't count. Only the stack's host pushes to it, and no agent can reach it with any credential. Import and new story files stay closed until the remote is set and checked, and each import's archive push waits until the remote holds the DOCX commit. This keeps "Git is used for all changes -- local and remote" true for DOCX chapters.
- Item 3 (the Hermes gate)
-- Item 4 (canon and playground convert, type folders stay Markdown)
+- Item 4. Canon and playground story files convert one file at a time, including files in unmarked folders inside a book folder or a playground. Type-folder and supporting files stay Markdown. A story file outside every book folder and playground has no universe or book, so it converts only after a human maps its folder into a book folder or a playground. The mapping step is Mos's reading of P1 and Q30.
The F4 measurement goes with them.
## Files the row would own
-As with 1.16, the row owns three files:
+The id count goes from 85 to 87. 1.16 owned three files because its count stayed at 85. Like 1.13, the last version that added ids, the row owns four files:
- `docs/prd/mosaic-stack.md`
-- `packages/tasks/requirements.json`: the version, the two new ids with their titles (REQ-LIB-8 and REQ-LIB-9), and the changed titles of REQ-LIB-1, REQ-LIB-2 and REQ-LIB-4.
-- `packages/tasks/tests/requirements.test.mjs`: the version pin and the id count of 87.
+- `packages/tasks/requirements.json`: the version 1.17, the two new ids REQ-LIB-8 and REQ-LIB-9 with their titles, and the changed titles of REQ-LIB-1, REQ-LIB-2 and REQ-LIB-4. The new ids go after REQ-LIB-7 and before REQ-INST-1, in the same order as the PRD's bullets.
+- `packages/tasks/tests/requirements.test.mjs`: the version pin 1.17, the id count of 87, and the duplicate-id message, which goes from `requirements[85].id: REQ-ROLE-1 twice` to `requirements[87].id: REQ-ROLE-1 twice`.
+- `packages/bus/tests/requirements.test.mjs`: the list length, which goes from 85 to 87.
The gate would match 1.16's:
1. A reviewer approves the text.
2. Jason says yes to the exact text through Mos.
-3. `requirements.test.mjs` and every `scripts/test-*.sh` that reads the PRD or `requirements.json` are green on Sage's gate.
+3. Both `requirements.test.mjs` files, in `packages/tasks/tests` and `packages/bus/tests`, and every `scripts/test-*.sh` that reads the PRD or `requirements.json` are green on Sage's gate.
4. Mos gets the landing commit.
+132
View File
@@ -0,0 +1,132 @@
--- DRAFT-d8.md 2026-10-23 06:16:54.374694135 -0500
+++ DRAFT-d9.md 2026-10-23 10:40:54.806553704 -0500
@@ -1,6 +1,6 @@
# PRD 1.17 draft: Case 2 story files are DOCX (Q235 to Q237, P1 to P6)
-Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 8. It is d7 plus Mos's d8 fix list mos-1443 (see "d8 changes"). d7 is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
+Sage, 2026-10-23, for mos-1366 item 1 and Vikunja 570. Draft 9. It is d8 plus Jason's Q41 answer O2 (DECISIONS Q239, mos-1467), which makes J-b yes, Webby's round 5 N15 to N17 (webby-0210) and Webby's round 6 B1, N19 and N20 (webby-0212) (see "d9 changes"). d8 is d7 plus Mos's d8 fix list mos-1443 (see "d8 changes"). d7 is d6 plus Mos's ruling mos-1379 on Fenton's 571 B2 (see "d7 changes"). d6 is d5 with Webby's round 3 (webby-0207) fixed: B1 to B3 and N10 to N13, with N14 left for Mos (see "d6 changes"). d5 folds in:
- Mos's d4 check ruling mos-1372 (D1 to D14; D15 was dropped).
- mos-1374, which replaces J-c.
- The impact map mos-1368 (jason-master-restart d183246, `projects/mosaic-mvp/case2-docx-impact-map.md`: A1 to A16, critic items C1 to C6, items 2 to 7).
@@ -103,10 +103,11 @@
### REQ-LIB-2
-Replace "A file's scope comes from its path alone, prefixes first: a Markdown file
-under a supporting prefix is supporting, one under a playground prefix at any level
-is playground, any other Markdown file in a book folder is canon, and everything
-else is unmapped and never returned. A folder in both lists is supporting." with:
+Replace "A file's scope comes from its path alone, prefixes first: a Markdown
+file under a supporting prefix is supporting, one under a playground prefix at
+any level is playground, any other Markdown file in a book folder is canon, and
+everything else is unmapped and never returned. A folder in both lists is
+supporting." with:
"A Markdown file's scope comes from its path and from past imports, prefixes
first: a Markdown file under the archive folder is archived, one under a
@@ -191,10 +192,13 @@
Replace "The user sees them in Obsidian's graph and backlinks." with "The user sees
a Markdown file's links in Obsidian's graph and backlinks. A DOCX story file's
wikilinks stay clickable in the Console's editor (O2 design D11), and no graph
-shows them yet."
+shows them yet. The Console's Connections view lists the current links and
+backlinks of each story file the user may see, in both formats, with each one's
+scope and format."
Version tag: 'and "Connections are always visible".)' becomes 'and "Connections
-are always visible". Version 1.17, with Q222 for the two settings.)'
+are always visible". Version 1.17, with Q222 for the two settings and Q239 for
+the Connections view.)'
### REQ-LIB-6
@@ -273,8 +277,9 @@
- Each stage of an import writes a logged event. The import's record on the
stack's host names the source file's path, commit and blob and the new
document id, and each event points to that record.
- - After a conversion, a Markdown file at the converted file's old path is neither
- canon nor playground. Lookups leave it out, the format map flags it, and the human is told.
+ - After a conversion, a Markdown file at the converted file's old path is
+ neither canon nor playground. Lookups leave it out, the format map flags it,
+ and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
canon until its import, unless it sits at a converted file's old path or under
a playground prefix. The Console flags it and offers the import. Nothing
@@ -413,8 +418,8 @@
push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
- stood at conversion, until a Console view of their links exists. Q226
- records that view as a later option.
+ stood at conversion, and the Console's Connections view shows their
+ current links.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
@@ -451,8 +456,8 @@
not `git diff`. (Version 1.17.)"
- "A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
chapter only through its archived Markdown, and no DOCX edit reaches that file's
- links. The DOCX's current links show only in the Console's editor until a
- Console view of them exists. (Version 1.17.)"
+ links. The DOCX's current links show in the Console's editor and its
+ Connections view, and no graph shows them yet. (Version 1.17.)"
- "Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
@@ -467,6 +472,15 @@
import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)"
+## d9 changes (Q239 and Webby round 5)
+
+- J-b yes (Q239, mos-1467), in the five edits mos-1443 R3 set for a yes (Webby round 6 B1). R3.1: REQ-LIB-5's replacement gains the Connections view sentence after "no graph shows them yet.". R3.2: the graph Risks line ends with the editor and the Connections view. R3.3: the A6 line moves from "Not taken" under A6. R3.5: the REQ-LIB-5 note ends with the Connections view, which drops "until a Console view of their links exists" and "Q226 records that view as a later option". R3.4 is Gyro's design edit at 767bb62c7 (writing-editor.md:188-190, :240-244 and :1272, docx-citations.md:66), and it follows in his docs round. Beyond these and N21, nothing else in the PRD text changes.
+- N19 and N20 (Webby round 6): J-b's question text is in the past tense, and the line that the import doesn't wait for the view is cited as Mos's R3 default. Mos confirmed it at his J-b check (mos-1470).
+- N21 (Webby round 6, Mos yes in mos-1470): REQ-LIB-5's version tag cites Q239 for the Connections view beside Q222 for the two settings.
+- N15: the D7 landing line gives R8's wording beside the impact map's quote.
+- N16: the d7 changes line notes that R8 replaces "no agent credential reaches it" in d8.
+- N17: the REQ-LIB-2 Replace quote and the REQ-LIB-8 old-path bullet are rewrapped under 82 columns. Their words are unchanged.
+
## d8 changes (Mos's d8 fix list, check wf_63bba879-180)
- R1: REQ-LIB-4's tail covers DOCX saves, which agents see at their next lookup after the editor reports them as committed. The notes quote the whole 1.16 sentence. Source: nit/jason/F10.
@@ -535,7 +549,7 @@
- REQ-LIB-5 gains Mos's three sentences after the extracted-text sentence. The text agents read has tracked changes accepted. Comments, and text with tracked deletions shown, each wait for a setting only a human turns on, off by default. Its version tag cites Q222 for the two settings, which is the only text here beyond Mos's.
- J-d gains Mos's sentence on comments and tracked deletions.
-- Fenton's B1 is design only. d6's "no agent credential reaches it" holds, and the shared helper stays #431.
+- Fenton's B1 is design only. d6's "no agent credential reaches it" holds (R8 replaces it in d8), and the shared helper stays #431.
## d6 changes (Webby round 3, webby-0207)
@@ -556,7 +570,7 @@
- D4: REQ-LIB-8's one-way bullet, which carries the archive refusal and consequence F2's text, and the 571 list item.
- D5: it landed in REQ-LIB-2's "a Markdown file at a converted file's old path is unmapped" in Webby's B2 form, and in the REQ-LIB-8 old-path bullet.
- D6: REQ-LIB-5's freshness sentences.
-- D7 and mos-1375: REQ-LIB-4's remote sentences, the remote step in REQ-LIB-8's order bullet, the fourth opening condition and the 1.17 note on when conversion opens, plus the 571 list item on W4 section 7.3, which replaces the no-remote rule of the old W4 section 7.4. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it".
+- D7 and mos-1375: REQ-LIB-4's remote sentences, the remote step in REQ-LIB-8's order bullet, the fourth opening condition and the 1.17 note on when conversion opens, plus the 571 list item on W4 section 7.3, which replaces the no-remote rule of the old W4 section 7.4. The impact map's item 2 adds "only the stack's host pushes, and no agent credential reaches it", which R8 words in d8 as "no agent can reach it with any credential".
- D8: the REQ-LIB-5 replacement, the import bullet's wiki-link sentence, the Risks line, the note quoting the old sentence, and "REQ-LIB-1 only gains sentences".
- D9: the 1.17 note that agents read a converted story file through the Case 2 lookups, from the d4 check's consistency B2, and J-d, which puts the read model to Jason as his call.
- D10: the d4 check's B5 sentence, in REQ-LIB-8.
@@ -573,6 +587,7 @@
- A3: the import is outside the vocabulary, the hard-wait list is unchanged, and the archive move isn't `data.delete`. The C6 code site is in the 571 list.
- A4: the section lead and REQ-LIB-1's writing-store sentence.
- A6: format and location in every answer, extracted text with DOCX locators, links across formats.
+ - A6's named chapter-links view. D8 took B8's "no graph shows them yet", and J-b put the view to Jason. His O2 (Q239) takes it in d9 as REQ-LIB-5's Connections view (mos-1443 R3).
- A7: read-only extends to the store and the archive, no agent action on a story file in any format, no agent DOCX write, and role contracts state the formats. C1's role contract line is a 571 build item.
- A8: the DOCX cite shape, and a cite naming an archived file as current is refused.
- A9: a hybrid sees a source's DOCX canon through REQ-LIB-2's source scope, and REQ-LIB-8 lets an exclusion name a DOCX document by its id. The id form lets the human exclude a single DOCX started in the editor, which has no old path. An archived file isn't canon, so no hybrid sees it.
@@ -593,12 +608,11 @@
Not taken:
- Impact map item 4's "files in unmarked folders are eligible one at a time", for folders outside every book folder and playground only. Their files are unmapped under REQ-LIB-2, so a DOCX made from one would have no universe or book to keep. The human maps such a folder into a book folder or a playground first, under REQ-LIB-1. A file in an unmarked folder inside a book folder or a playground is canon or playground, and the import takes it one file at a time.
-- A6's named chapter-links view. D8 takes B8's "no graph shows them yet", and J-b puts the view to Jason.
## Marked for Jason (additions in the text he sees)
- J-a. Whether "We will not run the systems in parallel" applies per chapter or as one switch per book. His call. As written, it applies per chapter.
-- J-b. A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the chapter only through its archived Markdown, and no DOCX edit reaches that file's links. REQ-LIB-5 still cites two TARGET lines, "Visualization of data via Obsidian or equivalent is required" and "Connections are always visible". Mos's reading is that 1.17 as written meets neither line for converted chapters. Their current links show to the user only as clickable text inside the editor, and no graph or list shows them. Q226 leaves room for "another way to handle the visual brain map". The O2 design that landed for Vikunja 571 at 767bb62c7 holds a Console Connections view and its tests as later work that waits for this answer. His call whether 1.17 requires that view. As written, it does not. Mos recommends that it does, because impact map A6 says "Recommended: name the view" and TARGET says "Connections are always visible".
+- J-b. A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the chapter only through its archived Markdown, and no DOCX edit reaches that file's links. REQ-LIB-5 still cites two TARGET lines, "Visualization of data via Obsidian or equivalent is required" and "Connections are always visible". Mos's reading was that 1.17 as d8 wrote it met neither line for converted chapters. Their current links showed to the user only as clickable text inside the editor, and no graph or list showed them. Q226 leaves room for "another way to handle the visual brain map". The O2 design that landed for Vikunja 571 at 767bb62c7 held a Console Connections view and its tests as later work that waited for this answer. His call was whether 1.17 requires that view. As d8 was written, it did not. Mos recommended that it does, because impact map A6 says "Recommended: name the view" and TARGET says "Connections are always visible". Jason answered O2 (Q239): 1.17 requires the view. d9 writes it into REQ-LIB-5, its note and the Risks line, in the text of mos-1443 R3. The view comes with the conversion work after 10-27, and the 10-27 Case 2 release stays on Markdown (Q235 P6, Q237). That the import does not wait for the view is Mos's default in mos-1443 R3. Q239 doesn't address it, and Mos confirmed it at his J-b check (mos-1470).
- J-c (mos-1374): "The store pushes to a remote (D7), and the Case 2 VM is in the nightly PBS backup. For a fixed restore point before her first real import, a manual backup of VM 1012 with PBS protect runs first. That step is reversible and needs no reboot." Mos's reading is that this sits inside the P5 go step, so it needs no new question.
- J-d. How agents read a converted chapter, D9. His call. As written, an agent reads it through the ordinary Case 2 lookups in any thread or room, under the same project and universe access as Markdown canon. This is Mos's reading of F41 and REQ-LIB-7. The other choice is the model in the earlier O2 design, W4 sections 7.1 and 7.2. Under it, an agent reads a converted chapter only in a new writing conversation that holds one human and one agent, only after the business owner grants that agent access to that document, and by default only what the human attaches to each message. Comments and tracked deletions stay hidden from agents until a human turns on a setting for each. Markdown has neither, so the default keeps agents at the access Markdown gives them. Those two sentences are Mos's ruling mos-1379. A yes to the text as written settles it.
- J-e. Versioning means the editor's tracked changes plus one git commit per save.
+269
View File
@@ -0,0 +1,269 @@
# Row 252 build notes: Console inbox Approve and Deny for grant requests
Seat: Scrooge. Reviewer: Webby. Issue #1753, Vikunja #435 (J2).
Brief: `docs/plans/2026-10-21_console-inbox-approve.md` (blob b41676d06).
Design: Gander's DESIGN.md at d218e14bd, approved by Webby at 5d8f0f4b5 (webby-0082) with manifest checks N1-N5.
Base: aca16404c on branch `scrooge/row252`. Rebase onto the row 253 tip under a new branch name once 253 lands.
## Status
- Code, tests and docs are written. No test has been run yet, because Gander holds the gate slot for row 253 run 5 and Sage ruled no node until a CLEAR.
- Everything in the Results section is empty until that run. Nothing below claims a pass.
## Q188
No agent creates a grant. Approve and Deny are human-only Console actions.
- Only a console cap reaches `decision.preview.console` and `decision.resolve.console`. A console cap reaches nothing else (console-decide `J1, Q188`).
- The socket refuses a console proof for any other verb before the proof check (`server.mjs`).
- A console cap binds only through `authenticateConsole`, which needs the console proof (section 2): the same-tree `console-cli.mjs` wrapper, its own nonce, a direct same-tree `serve` parent, and a configured owner subject from the spawn environment.
- `recordEvent` keeps `human.input` reserved, so no agent writes the record that lets a console close start the executor (console-decide `recordEvent keeps human.input reserved`).
- A `via console` close without its matching `human.input` approves nothing (console-decide `J14`).
## Manifest checks N1-N5
- **N1.** Every `console.sso` subject is an owner of the business. Name is display only.
- `cli/src/config.mjs` carries `{provider, iss, sub}` per subject into the boot as `consoleOwners`. No sso means no key.
- `bus/src/runtime.mjs` validates the list (array, at most 512, exactly three non-empty strings each) and passes it to `verifyPrincipal`.
- Tests: cli config.test `Row 252 (Webby N1)` and principal.test.
- **N2.** `human.input` is written in the same transaction, before `#approvedGrant`. The executor starts once, and a deny starts none.
- Tests: console-decide `N2, J13` checks the order by seq and that the grant runs once. `J15` checks that a deny starts nothing.
- **N3.** The close notice comes from `from_run 'human-console'`, and `public/bus.js` shows "via console". `landing` and `#protectedEnd` stay CLI-only.
- Tests: console-decide `N2, J13` checks the notice. decide-browser `J17` checks the close-elsewhere line.
- **N4.** `decision.resolve` is not a Console verb.
- `CONSOLE_VERBS` is exactly the two console verbs. The webui serve `BUS_VERBS` read list has no decision verb, and webui `cli.mjs` has no `decision.` string. `consoleCall` refuses anything outside `CONSOLE_VERBS`.
- Tests: console-decide `N4`, and decide-sso `N4`, which hits `/api/bus/decision.*` for 404 and covers the cli.mjs scan and the consoleCall refusals.
- **N5.** `readProcess` keeps the five `MOSAIC_BUS_CONSOLE_*` keys in the env allowlist (`bus/src/human.mjs`). The human proof and its spawn are unchanged.
## Design choices and tradeoffs
- **Webui imports from the broker.** `webui/src/bus.mjs` imports `CONSOLE_VERBS` from `bus/src/broker.mjs`, which loads the broker module into the Console at import.
- I chose one source for the verb list over a second copy that could drift.
- If Webby prefers the copy, the alternative is a literal list plus a test pinning it equal to `CONSOLE_VERBS`.
- **The Console transport shares humanCall's spawn.** `consoleCall` and `humanCall` share it as `transport()`, so `webui/src/bus.mjs` changed. The busCaller `business` default keeps `||` (the old falsy semantics), not `??`.
- **Confirmations are held in memory in the serve process.** Their lifetime is 2 minutes, they are single use, and they bind the session, decision, choice and digest. The cap is a limit that never drops a live confirmation.
- A restart loses all held confirmations. The user clicks again and gets a new preview. Nothing is written until the resolve.
- **No retry anywhere.** A resolve whose bus reply is lost answers `outcome-unknown`, the confirmation is spent, and the page says "Nothing was sent again."
## Old tests changed (for Mos, per mos-1272)
Mos ruled both changes allowed in mos-1321 (17:12:28Z), each with one condition.
- **shell.test condition:** reword the comment that opened "No Resolve control", since it is false once the row lands. It now says row 252's Approve and Deny is the one decision control, that its labels come from ACTIONS, and that the doesNotMatch keeps any literal label out of the markup. Webby checks the wording in review.
- **channel-proof condition:** a header comment is not enough, because human.mjs changes what readProcess keeps. In the review, Webby reads the human.mjs and webui bus.mjs diffs and states two things:
- the human proof's checks are unchanged
- only the console verifier reads the five MOSAIC_BUS_CONSOLE_* keys, and each case is covered by a named test in console-decide or decide-sso
- **Second row to land (252 or 239):** it recomputes the webui bus.mjs hash, adds its own header line, and its reviewer re-reads bus.mjs from the base to the combined tip.
- **`webui/tests/shell.test.mjs`**, write-boundary test. Allowed under mos-1321, with the comment reworded.
- The `writes` list gains the new `fetch(url, ...)` in `decidePost`. It is first in file order (bus.js:362).
- The `method:` count goes from 7 to 8.
- A new assertion pins `decidePost`'s callers to `url` plus the two literal `/api/decisions/${enc(a.id)}/...` routes.
- No existing entry was loosened.
- **`bus/tests/channel-proof.test.mjs`**, sha256 re-pins with a header comment:
- `bus/src/human.mjs` (N5 env keys) to bb13c146…c885.
- `webui/src/bus.mjs` (consoleCall and `transport()`) to 701f009e…5272.
- Any later edit to either file needs a re-pin.
- `bus/tests/principal.test.mjs`, `webui/tests/tester-listener.test.mjs` and `cli/tests/config.test.mjs` have new tests appended only.
## Overlap with row 239 (Honker, b0c28366f, sage-2835)
- Row 239 also re-pins `channel-proof.test.mjs`:
- `packages/webui/src/bus.mjs`, to 1eba9774…e5cf. That is the pin both rows change.
- `packages/cli/src/transport.mjs`, which is 239 only.
- `packages/bus/src/human.mjs` is a 252-only pin.
- The two rows also share source files: `bus/src/broker.mjs`, `bus/src/runtime.mjs`, `webui/src/bus.mjs` and `webui/src/serve.mjs`.
- Whichever row lands second rebases onto the first and recomputes the webui `bus.mjs` pin from the merged file. Both header comments are kept. It then reruns every touched test file whole.
- Mos needs to see that re-pin (re-pins need Mos).
## Mutant plan (design section 6, J1-J17)
Each mutant is one edit to the named source. Run the killing test, then restore and compare the sha256 against the pre-mutant hash with `git status` clean apart from the build. Kill rules are written before the runs.
| J | Mutant | Source | Expected kill |
|---|---|---|---|
| J1 | eligibility from the request, `#consoleResolution` returns non-null for any decision | broker.mjs | console-decide `J1, J2` (ineligible) |
| J2 | hardcode `approve = yes`, `deny = no` | broker.mjs | console-decide `J2` (go/stop, deny listed first) |
| J3 | owner from name or header, skip the owner match in verifyPrincipal | principal.mjs | principal.test `console requires…` |
| J4 | skip the serve-parent walk | principal.mjs | principal.test `console requires…` |
| J5 | accept the human nonce namespace | principal.mjs | principal.test `console requires…` |
| J6 | reuse the session from the confirmation, skip the live session check at resolve | decision-http.mjs | decision-http `session that ends`, decide-sso `listener gates` (new session) |
| J7 | keep the console cap bound after the call (skip `release` for kind console) | server.mjs | **open**: no new test pins release for a console proof. Before the run, check whether an existing server test kills it. If not, add one and name it in the report. |
| J8 | skip the same-origin check | decision-http.mjs | decision-http `gates`, decide-sso `listener gates` |
| J9 | resolve without a confirmation token | decision-http.mjs | decision-http `a confirmation then a resolve`, `bad body` |
| J10 | drop the choice, decision or session binding | decision-http.mjs | decision-http `binds the session, decision and choice` |
| J11 | skip the digest, closed or business check | broker.mjs | console-decide `J11`, `N2, J13` (second answer closed) |
| J12 | do not spend the confirmation | decision-http.mjs | decision-http `a second resolve refuses`, `confirmations… one use` |
| J13 | write via cli, or omit the subject in human.input | broker.mjs | console-decide `N2, J13` |
| J14 | `#approvedGrant` accepts via console without the human.input | broker.mjs | console-decide `J14` |
| J15 | start the executor on any resolved choice | broker.mjs | console-decide `J15` and `J2` (stop starts nothing) |
| J16 | `decide` true without sso or send | serve.mjs, public/bus.js | decide-sso `J16`, decide-browser `J16` |
| J17 | retry on outcome-unknown or auto-submit after preview | public/bus.js | decide-browser `J17` (post count stays at 7) |
## Manifest
Source:
- packages/bus/src/broker.mjs
- packages/bus/src/client.mjs
- packages/bus/src/console-cli.mjs (new)
- packages/bus/src/human.mjs
- packages/bus/src/principal.mjs
- packages/bus/src/runtime.mjs
- packages/bus/src/server.mjs
- packages/cli/src/config.mjs
- packages/webui/src/bus.mjs
- packages/webui/src/cli.mjs
- packages/webui/src/decision-http.mjs (new)
- packages/webui/src/public/bus.js
- packages/webui/src/serve.mjs
Tests:
- packages/bus/tests/channel-proof.test.mjs (re-pins)
- packages/bus/tests/console-decide.test.mjs (new)
- packages/bus/tests/principal.test.mjs
- packages/cli/tests/config.test.mjs
- packages/webui/tests/decide-browser.test.mjs (new)
- packages/webui/tests/decide-sso.test.mjs (new)
- packages/webui/tests/decision-http.test.mjs (new)
- packages/webui/tests/shell.test.mjs (old test changed, see above)
- packages/webui/tests/tester-listener.test.mjs
Docs:
- packages/webui/README.md
- agents/scrooge/work/queue-252/BUILD.md (this file)
## Results
Not run. At the first CLEAR:
1. Run each touched test file whole, with TMPDIR under /mnt/storage/scratch/tmp.
2. Run the bus, webui and cli suites.
3. Run the mutants above, then the drift check against refactor.
## Rebase onto fe4ee8c7b (sage-3482, 2026-10-23)
The old base aca16404c had drifted. Since then refactor changed 8 of the row's 24 paths, across 15 commits. I made a new branch, scrooge/row252-fe4ee8c7b, so scrooge/row252 22cc74645 is untouched and nothing was force-pushed. I rebased with local git, `-c core.hooksPath=/dev/null`, and no stash.
Commit map:
- 4bd273bce -> 1891d36e4 (product and tests; six files conflicted)
- ca4537cc9 -> 9e5980acd (BUILD.md; clean)
- 22cc74645 -> 6fd206e80 (shell.test comment and BUILD.md; clean)
Conflicts, all in 4bd273bce, and how each was resolved:
1. packages/bus/src/broker.mjs, verb lists. On the base, row 206 added the share verbs to USER_VERBS, and row 275 added VIEWER_VERBS, userLevel and BOARD_VERBS and removed the USER_LEVEL const. I kept the base's lists and dropped USER_LEVEL, because nothing on the base reads it. CONSOLE_VERBS goes in after USER_VERBS with its comment unchanged. The request() gate (`!!s.console !== CONSOLE_VERBS.includes(verb)`) auto-merged after the FED R4 access sweep and the user check. Its order relative to the user gate is the same as on the old base.
2. packages/bus/src/runtime.mjs, startBroker. FED R4 (#1801) added `access.intervalMs` to the access check. I kept the consoleOwners validation and the frozen owners first, as in the row, and then the base's access check with intervalMs.
3. packages/cli/src/config.mjs. FED R6 (#1800) added accessFor and `config.access`. I kept both: accessFor and then consoleOwners, and in bootConfig `config.access` and then `config.consoleOwners`. process.mjs passes the boot config to startBroker whole, so both keys reach it.
4. packages/webui/src/cli.mjs. FED R6 moved the owner's bus options into consoleBus() (console-bus.mjs) and added userCaller and userOptions. I kept the base's form, added consoleCall to the bus.mjs import, and added decisionWrite to the declarations. I kept the row's `if (ssoOn && sendOn) decisionWrite = consoleCall(...)` after the consoleBus() call, and pass decisionWrite to startServer next to userOptions and federation.
5. packages/webui/src/serve.mjs. FED R6 added granted-user sessions: userRoute gates them, and userOptions rebinds the options per request.
- The signature now has both decisionWrite and userOptions/federation.
- The session hunk keeps the base's signer and granted-user block. ssoSession is set only for a session with no `user`, which is the owner's. A granted user is refused at userRoute before the decision route anyway, because /api/decisions/* is not on userRoute's list. Under the new base this keeps the design section 2 rule that only the owner answers a decision.
- /api/config keeps the base's `user` and `federation` keys. `decide` gains `!granted`, matching how the base gates `edit`, so a granted user's config never offers Approve and Deny.
- The decisionWrite startup check and createConfirmations() auto-merged unchanged.
6. packages/bus/tests/channel-proof.test.mjs, webui bus.mjs pin. Row 301 (#1808, mos-1449) moved the user transport into bus.mjs and re-pinned it to 39fcb27c. The row's consoleCall auto-merged on top. I kept row 301's comment and re-pinned to the merged blob's sha256, 604092cb706b152d458f299ff1071f6cd488774629012f96b672f7e9a16c0b2c. The human.mjs pin (bb13c146) auto-merged and matches the blob at the new tip. The row's existing header comment (Webby N5) still describes why bus.mjs is re-pinned. humanCall and transport() are unchanged.
Auto-merged without conflict: webui bus.mjs (consoleCall next to row 301's userCaller), webui README.md, bus client.mjs, human.mjs, principal.mjs, server.mjs and public/bus.js.
Two new-base reads I checked:
- granted-user.test.mjs reads `/api/config` key by key and has no deepEqual on the whole object, so the extra `decide` key doesn't break it.
- No other test pins one of the row's source files by sha256, apart from channel-proof.
Not run: no test, node or syntax check during this step. Sage's next CLEAR covers the push, the whole webui, bus and cli suites, every scripts/test-*.sh, and J1-J17. Plugins must show 0 failures (mos-1456/1462). J7 still needs a killer test. The serve.mjs `!granted` and ssoSession lines are new since the build, so they get mutants too. I'll add them to the J list at the run.
## Runs (sage-3488 CLEAR, 2026-10-23)
Gate tree: detached worktree of 3e6de9807 with the canonical node_modules linked, so
`MOSAIC_REQUIRE_EDITOR_BUILD=1` is set (mos-1453). TMPDIR comes from mktemp under
/mnt/storage/scratch/tmp. Each step runs under `timeout -k 10` (mos-1275):
| Step | Limit (s) |
|---|---|
| pkg bus, cli, plugins, webui, business (`node --test tests/*.test.mjs`) | 1800 each |
| test-auth / conductor / config / discord / extension-package | 126 / 145 / 130 / 147 / 130 |
| test-foundation / land-work / queue / release / task | 269 / 171 / 226 / 157 / 231 |
| mutant single file | 300, chat-browser 600 |
The script limits are 3x the longest runs recorded in queue-253 (receipt 3b91cfdaf).
### First-run reds placed (mos-1272)
- pkg-bus at 3e6de9807: 902 pass, 1 fail. The failure is `N2, J13` in console-decide.test.mjs:180, a new
test (0 human.input rows found, 1 expected). The cause is in the test, not the product. `events()` spread
the parsed body after the row's `kind`, so an answer body's own `kind: 'answer'` overwrote
`'human.input'` and the filter could never match. Three more filters on the same field had the same
defect, :250 and :290 (count of human.input must be 0) and :252 (an action.refused exists). The two
zero counts passed vacuously. Fix: the row's kind is also kept as `event`, set after the body, and all
four filters read `event`. The deepEqual at :181 now names `event: 'human.input'`. The file then ran
whole at 11 pass, 0 fail.
- J7 (open in the plan): no existing test bound a console proof over the socket. The new test
`J7: a console proof over the socket binds a cap for its one call and the reply releases it` in
console-decide.test.mjs serves a real socket with authenticateConsole, makes one console call that
answers and one that refuses (`decision-not-found`), and checks that both caps are released
(`unauthenticated`). J7 is applied as `boundCap = kind === 'console' ? null : transient;` in server.mjs,
which skips every release path for a console cap (finish, the finally, and socket close). It ran
at 11 pass, 1 fail, failing only the new J7 test. KILLED. The restored server.mjs matched its pre-mutant sha256.
- decide-sso.test.mjs (new) ran 2 pass, 2 fail before any mutant:
- `J16`: its `{ send: null }` case reached `setup`, and startServer refused it, because a decision
transport without `--send` never starts (serve.mjs guard). That case now asserts the refusal, and the
`{ decisionWrite: null }` case still checks the read-only routes and `decide: false`.
- `N4`: a POST to `/api/bus/decision.*` gets row 74's preview refusal (403 `read-only`, for every
write without a route of its own) before any verb lookup. POST now asserts that refusal and GET
still asserts 404. Either way the transport sees no call.
- The file then ran whole at 4 pass, 0 fail.
- Old-test reds, sent to Sage before any fix (status 2):
- cli `access-boot.test.mjs` R6 B1-B3 come from this row. The fixture's SSO provider has no issuer,
so `consoleOwners` maps `iss: undefined` and startBroker refuses the owner list
(`invalid-host-request`). The file is 4/0 on fe4ee8c7b.
- cli `compactions-launch.test.mjs` REQ-THREAD-2 T3, 5 tests, each "timed out waiting". They also fail
0/5 on a fe4ee8c7b tree, so they are not this row's.
- Correction (sage-3492): the compactions-launch reds were the environment, not a base red. The gate
TMPDIR (`<gate>/tmp`) was long enough to break the launcher/bwrap paths. Alone in a short
`mktemp -d /mnt/storage/scratch/tmp/scrXXXXXX` TMPDIR the file ran 5 pass, 0 fail, 0 skipped, and
Sage's own gate ran it green. The file is unchanged.
### Rulings and further placements (sage-3492 to sage-3496)
- access-boot (Mos GO mos-1464, relayed in sage-3494): `access-boot.test.mjs:17`'s `authentik` provider
gains `issuer: "https://idp.test"`, and nothing else changes. numstat against fe4ee8c7b reads 1 1.
It is committed alone (b316e0a55). access.test.mjs:273 uses the same fixture but never reaches
bootConfig, so it is untouched. The file ran 4 pass, 0 fail.
- decide-browser.test.mjs (new) ran 0 pass, 3 fail before any mutant, then 1 pass, 2 fail after the
first fix:
- setup's `UPDATE decisions SET question=?` hit migration 3b's `decisions_no_update` trigger, because
decisions is append-only. The test now writes its own open credential.mint grant decision, with the
hostile question in the INSERT. It is the same two rows host-fixtures' grantDecision writes, and
host-fixtures.mjs is unchanged.
- `page()` visiting the URL it was already on was a same-document hash change, not a load. So J17's
"closed elsewhere" step never re-read the bus, and the redirect test's second visit never redrew
after signing out. `page()` now loads about:blank first, as editor-browser.test.mjs:216 does.
- The file then ran whole at 3 pass, 0 fail (9e1601a20).
- decide-sso J16 (sage-3492): the startServer refusal stays. The one config that starts without send
also lacks a decision transport, since startServer refuses a transport without `--send`. That config,
`{ decisionWrite: null, send: null }`, is now in the J16 loop and gets `decide: false` and the
read-only routes. Webby rules in the manifest round.
- J19 killer (new test in decide-sso.test.mjs): an access document with one granted user (alice). The
listener has `--send`, a decision transport and a user transport. The owner's config says
`decide: true`. Alice's says `user: true`, `send: true` and `decide: false`. Her POSTs to both
decision steps get 403 `owner-only`, and the transport sees no call.
- J18 is equivalent alone, because a granted session never reaches decisionHTTP. At serve.mjs:694 a
session with `user !== undefined` meets `userRoute` before the decision branch. userRoute admits only
`/api/chat*` and GETs of `/api/config` and the user files, never `/api/decisions/*`, so the 403
`owner-only` comes before `ssoSession` is read. The `session.user === undefined` guard is a second
layer behind that. The J19 test's owner-only POSTs pin the first layer.
- J11c is equivalent. `s.human !== b.human` at broker.mjs:5604 can't hold for a console cap:
- bindConsole (broker.mjs:1459) refuses `record.human !== b.human` with `human-required`.
- Its cap carries `human: b.human` for its own business, and #cap freezes the session (:1094).
- At resolve, `b` is `this.#business(s.business)` (:5324), the same business.
- reloadBusiness refuses a document whose human differs (:517).
So for any session with `s.console` set, the two humans are equal. Webby rules on it in the
manifest round.
- Mutants, part 2, on the tip test files (decide-sso 5/0, decide-browser 3/0 as the baseline).
Every restore matched its pre-mutant sha256.
- J16a: KILLED by decide-sso J16.
- J16b: KILLED by decide-browser J16.
- J17a: KILLED by decide-browser J17 and the redirect test.
- J17b: KILLED by decide-browser J17.
- J19: KILLED by decide-sso J19. granted-user stays 19/0 under it.
- J18: SURVIVED decide-sso 5/0 and granted-user 19/0. It is equivalent alone, for the reason above.
- Old webui stubs (Mos GO mos-1465, relayed in sage-3497): library-cli.test.mjs:17 and
thread-claims.test.mjs:58 build a loader-hook stub of bus.mjs with no `consoleCall` export, and cli.mjs
now imports it. Each busSource gains `export const consoleCall=()=>{throw Error('consoleCall outside --sso')};`
in the neighbours' throwing form, and nothing else changes. numstat against fe4ee8c7b is 1 1 for each.
Alone they ran 1/0 and 4/0. No mos-1316 item for compactions-launch: the whole cli rerun is the evidence.
+20
View File
@@ -1575,3 +1575,23 @@ are never rewritten or removed; corrections are new entries.
- 2026-10-23T07:59:57Z | filbert (Claude Opus 5.5, T3 048f3c63) | row 299 manifest round 1 (#1806, Vikunja 573, Gander, manifest 277ace0b on ffa93b054) | approve in 31084, rev 3485; static on git objects (blobs = reviewed 1ec2b9765, product = base 80793d824); gate receipts 63db96d8b read, not rerun: case1 53/0 x3, cli 917/0 (59 files), M1-M6 killed; N1 M1 margin 0.02 s
- 2026-10-23T08:10:29Z | sage (Opus 5.5, 1ef1e4f8) | 299 gate and landing, 303 review, 219/295 rebases | 299 LANDED ffb2fedfb 08:03Z (done 3487, #1806 c31087); queue-commit 8a35ffadb (revs 3474-3487); 303 Duckworth approve rev 3480; requests posted as sage for 303 (c31082) and 299 (c31083); 219 full restart on bfa421b6c from 08:08Z (sage-3283)
- 2026-10-23T10:48:40Z | sage (Opus 5.5, 1ef1e4f8) | 302/295/219 landings, Docker pool diag, 590/591 briefs, 272 rebase | 302 LANDED 3ed9fd754, 295 LANDED 2efd127ce, 219 LANDED 5fdfd7275 (#1720 c31110, P19 416 c913); test-release 11/3 from exhausted Docker pools, 18 networks removed (mos-1437/1438); queue-commit 90633c864 (revs 3495-3510, 3510 Huey's); 272 rebase and fallback ruled (sage-3357)
2026-10-23T11:03:46Z | Gyro (Codex gpt-6.1-sol, mos-1444) | Vikunja571 PRD d8 docs alignment | R6 vocabulary boundary, mapped-folder eligibility and R14 whole-host recovery updated in parent/W4; static checks only, no tests or live changes. Fenton reviews; Sage lands.
- 2026-10-23T11:47:32Z | filbert (Claude Opus 5.5, T3 048f3c63) | row 305 round 1 (#1812, Vikunja 591, Gizmoduck, candidate 7e8218a2e) | approve in 31127, rev 3534; static on git objects (scripts = 3bce852d5; traps kept, no compose down, ls failure fails, skip only on docker info); gate receipts read, not rerun: Gizmoduck run1 + Sage gate task 92/0, release 15/0, mutants a/b red, c survives
2026-10-22 | Huey (Codex gpt-6.1-sol, cabd45ca) | Row 272 / 264e (#1777), sage-2796, huey/row272 on 2d0d407ba | CLI/Console session work in preparation; test execution/claim/push deferred under Gander's gate, no runtime or live change.
2026-10-22 | Huey (cabd45ca) | Row 272 additive 264d contract gizmoduck-0272 at 78e14352f | Draft cancellation UI/CLI and terminal receipts added; tests authored but not run under sage-2816 gate, no commit/push/live change.
2026-10-23 | Huey (cabd45ca) | Row 272 sage-3141 local rebase | Draft reapplied from 2d0d407ba onto b23783f39 on huey/row272-b23783f39; product merge clean, append-only log conflicts retained both sides. Landed 270/271 contracts checked; no Node/tests/claim/commit/push pending CLEAR.
2026-10-23 | Huey (cabd45ca) | Row 272 sage-3145 draft preservation | Shared stash commit 07dc9d2f7 pinned to huey/row272-stash-keep; rebased draft committed locally by explicit Sage instruction before tests. Untested checkpoint only; claims/test/push CLEARs still pending.
2026-10-23 | Huey (cabd45ca) | Row 272 Scrooge early static read on 850b22680 | B1 and N1/N2 follow-up drafted with new tests only; old assertions/shared stub unchanged. Static diff clean; no tests/commit/push pending CLEAR, round 1 remains frozen.
2026-10-23 | Huey (cabd45ca) | Row 272 round 2, sage-3354/3357/3358/3361 | Rebased onto 5fdfd7275 on huey/row272-5fdfd7275, preserving 219/295 and approved old-test hunks; rev3510 in-progress. New fallback test ran RED first in Sage's slot: expected legacy failure, 3 guards pass; slot returned. Bounded fallback and extra guards/mutants prepared for Sage's full gate; no post-implementation run or green claim.
2026-10-23 | Huey (cabd45ca) | Row 272 round 3, mos-1455 / sage-3435 | Added empty role authority arrays to new CLI fixture and guarded null old at all three chat render sites; browser tests byte-identical. Same 17-path manifest.r3, no local execution or queue writes. Follow-up candidate awaits Scrooge delta review and Sage gate; gate run 1 remains RED.
2026-10-23 | Sage (1ef1e4f8) | Rows 272 and 307 landings, row 306 design, row 252 rebase check | 272 landed d33989230 and 307 landed d243f6b25, both on full five-suite gates with mutants killed. Row 306 design r2 approved by Dewey (c31197), build going under mos-1463. Row 252 rebase 3e6de9807 on fe4ee8c7b checked and Scrooge given the slot (sage-3488).
2026-10-23 | Sage (1ef1e4f8) | Rows 291 and 306 landings, row 308 launch | 291 (PRD 1.16, #1796) landed 4874d1e83 and 306 (#1813) landed 1676ad0de..2d0318a44 by temp-index replay, patch-ids equal, both on all-suite gates (mos-1455, plugins 0 fail, 306 mutants M1-M3 killed). 306 N1/N2 in DEFERRED 9c8877bdf. Row 308 (PRD 1.17, #1815) briefed and launched to Gosalyn; slot passed to them for claim, push and in-review. Scrooge told to rebase 252 on 9c8877bdf. Mos told (sage-3515, sage-3520).
2026-10-23T17:29:46Z | Gyro (Codex gpt-6.1-sol, sage-3542) | 571 REQ-LIB-5/Q239 Connections design amendment | Proposed W8 plus parent/W4 alignment; Mos build order kept distinct from import acceptance. Static docs checks only, no repo Node/tests/live action. Fenton review, Sage landing.
2026-10-23T17:32:37Z | Gyro (Codex gpt-6.1-sol, sage-3544) | 571 Connections N1/N2 | W7 opening-order cross-references and human-view comment/deletion exclusion clarified. Static docs checks only; no repo Node/tests/live action. Delta to Fenton, Sage lands.
2026-10-23T17:48:43Z | Scrooge (629b6cd3) | Row 252 Console inbox Approve and Deny (#1753) | Rebased onto 306 landing as 580037105 (range-diff all =), slot run bus 907/0 webui 410/0 cli 943/0, in-review revs 3608/3609, Webby r1 APPROVE, J1-J19 specs to Sage; Sage landed 45ea3fa0d (done rev 3616). Part 1 mutant results were never in BUILD.md; they went to Sage by T3 (scrooge-0252-mutant-specs).
2026-10-23 | Sage (1ef1e4f8) | Rows 308 and 252 landings, 571 Connections design amendment, Vikunja 597 | 308 (PRD 1.17, #1815) landed 30e6bb680 and 252 (#1753) landed 45ea3fa0d (replay of 580037105 onto 30e6bb680, range-diff all =), each after a full gate on the landed tree (252 also J1-J19, J11c and J18 equivalent); Gyro's 571 amendment (Fenton APPROVE) landed e8bb13df8; Vikunja 570, 435 and 597 done, canonical webui esbuild 0.28.2; queue revs 3589-3616 committed 59f013247
2026-10-23 | Sage (1ef1e4f8) | Vikunja 571 W build rows; rows 13, 42, 257 and 289 check | Filed the design's ten W rows as queue rows 309-318 (revs 3617-3626, owners set to unassigned 3627-3636, queue commit 293b42fee pushed); Vikunja 571 done (comment 994). Row 13's candidate fd72d2689 is on refactor, gate is Monday's row 7 ledger run; 42 paused under mos-1184; 257 post-v1; 289 waits on 214 and 288. Slot released 17:56Z.
2026-10-23 | Fethry (Codex gpt-6.1-sol, 14f80e3c) | Row 225 C2-10b (#1726), Q245 handoff | Recovered Morgana's 9a131e956 by fast-forward on fethry/row225 in the assigned T3 worktree. Static review and contract confirmation in progress; all Node/tests require Sage CLEAR. No acceptance claim.
2026-10-23 | Fethry (14f80e3c) | Row225 T1 lane A sage-3641/3647 (#1726) | Continued Morgana handoff, fixed nested status limits and wrong-project revert receipt after RED, kept invalid-mapping diagnosis visible with RED/GREEN. Renderer9/0, routes13/0, browser9/0, full library81/0, channel-proof2/0; isolated mutants14/14, restored SHA256 checks and clean worktree sources. Old-test approval mos1510 preserved80/66 and3/1. Slot returned with no processes around19:29Z. Dewey source-review packet prepared; full webui/build and integration gate remain pending.
+113 -14
View File
@@ -187,8 +187,10 @@ canon Markdown chapter takes its position by that rule or a human's committed or
import preserves its chapter ID and position. Human reordering commits the manifest.
library_manuscript and next_chapter use this order, never archived paths. Each
answer/page names format, owned location/ID and captured commit. DOCX wikilinks stay clickable
in the Console editor; no graph shows their current links yet. A Console Connections view
and its tests are later work, waiting for Jason's J-b answer in PRD 1.17. Obsidian's archive
in the Console editor, and no graph shows them yet. Under PRD 1.17 REQ-LIB-5 and Q239,
the Console's Connections view lists the current links and backlinks of each story file
the user may see, in both formats, with each one's scope and format. W8 owns that view
and its tests. Obsidian's archive
graph is historical, not a live DOCX graph. No graph implementation is in these W rows. New Story chooses universe/book or playground and gets
its place in that manifest from its first DOCX commit, without an MD copy.
@@ -242,8 +244,8 @@ follow stable identities without an agent rewriting story links.
Obsidian's old Markdown graph is historical after conversion. It does not track later DOCX
edits, and it is not a parallel writing surface. The format map names current files and
clickable editor links resolve across formats; it is not a graph view. No graph shows DOCX
links yet. The later Connections view waits for Jason's J-b answer; it and its tests are
outside the current W3/W7 scope. No Markdown export or Obsidian graph parity is promised. `.doc`/`.qxd` imports remain separate D23 work.
links yet. W8 supplies REQ-LIB-5's Console Connections view: current links and backlinks
of each story file the user may see, in both formats, with each one's scope and format. No Markdown export or Obsidian graph parity is promised. `.doc`/`.qxd` imports remain separate D23 work.
D12. Q235–Q237 withdraw the superseded-notice proposal and its open-Q36 boundary. Current
Case 2 chapter reads after conversion use W4b's committed DOCX reader/citations. Agent
@@ -265,7 +267,9 @@ After each file's conversion publication, the authorized format map is a typed h
sourceCommit,conversionCommit}`. kind is chapter or supporting-document; current chapter
format is docx, supporting format markdown; state is current or converted-archive. scope distinguishes canon, playground, supporting
and archived without format-based promotion. Type-folder entity notes remain Markdown;
an unmarked-folder story is eligible for human selection, one file at a time (mos-1368 Q18). location
a story file in an unmarked folder inside a book or playground is eligible for human selection,
one file at a time. A story file outside every book and playground is unmapped and converts
only after the human maps its folder, per PRD 1.17 REQ-LIB-8 and mos-1368 Q18. location
is an opaque owned location, never a host path/capability. Publish only entries the caller's
live scopes allow, no title/path leak from denied documents. Agents get this map through
reviewed library/universe lookup results and normal authorized Case 2 context so they know formats
@@ -321,8 +325,9 @@ amendment, W4b PASS and live committed-reader/citation readiness, W7 reviewed im
release, Hermes retirement (#423 step 5, with its old write/store access removed), verified
host-only off-host durability and an authenticated human start. `library.convert` is a
human-only CLI/Console verb run through the host async processing adapter, outside agent
vocabulary/role grants and off the read-verb list. Its action remains a hard wait under
REQ-WAIT-2/3 until the approved PRD says otherwise; it never joins ROUTED silently.
vocabulary/role grants and off the read-verb list. It is not in ACTIONS or ROUTED and never
reaches the broker. A role file naming an action outside the vocabulary is already refused.
REQ-WAIT-1 and its hard-wait test remain unchanged (mos-1444 R6).
`editor.conversionHumans` is human-only, default business owner only. Folder setting/mapping
have one authoritative archive-prefix value; the library mapping and editor cannot disagree.
Archive prefixes cannot overlap playground/supporting prefixes. Conversion preserves bytes
@@ -576,12 +581,19 @@ runs Jason's layout agent nor automatically grants it access or sends book text
All rows follow BRIEF-TEMPLATE. They are not queue entries. S0 M1–M14 remain binding throughout W2/W6; source/target
changes return to security review. W7 adds the policy conversion/routing row. S0 and the original W4 design have reviewed verdicts; this policy revision goes to Fenton.
Their implementation gates remain separate. No code row W1/W2/W6/W3a/W3b/W3/W4b/W5/W7 enters the queue
Their implementation gates remain separate. No code row W1/W2/W6/W3a/W3b/W3/W4b/W5/W7/W8 enters the queue
before S0 PASS (recorded for this baseline, with M1–M14 still binding). Each also observes its explicit dependencies, source ownership, the CSP gate
and the MVP gate-slot priority. Q177 permits chaining reviewed candidates, not overlapping
owners. Sage assigns model-only seats. Each implementation reviewer is Opus 5.5 and did not
author the code. Fenton reviews this design and can review code only when not its author.
Build order (mos-1470, Vikunja 571 comment 961): W7 first supplies reviewed conversion/format
routing code with production import still closed, then W8 lands the Connections view, then
W7's human opening step may open the first real chapter import. W8 is after W3/W4b and
W7's closed routing candidate, before that W7 opening step. This is Mos's build-order rule,
not a new REQ-LIB-8 condition or an addition to import acceptance. No converted real chapter
exists without the view. O2 code remains after 10-27 and never ahead of MVP rows.
G1. Future code acceptance runs each touched test file independently and affected package
suites on Sage's slot after MVP priority, plus required repository checks. Source builds need
pinned tools, reproducible hashes, license/source manifests and independent review. Integration browser
@@ -1155,7 +1167,7 @@ The source archive and private DOCX repository cannot publish atomically through
`packages/bus/tests/{writing-save.test.mjs,chapter-format.test.mjs,schema.test.mjs,conversion-events.test.mjs}`,
`packages/harness/src/tools.mjs`, `packages/harness/tests/chapter-format.test.mjs`,
`packages/business/src/{vocabulary.mjs,business.mjs}`,
`packages/business/tests/{vocabulary.test.mjs,business.test.mjs,hard-waits.test.mjs}`,
`packages/business/tests/{vocabulary.test.mjs,business.test.mjs}`,
`scripts/mosaic`, `packages/cli/src/library.mjs`, `packages/cli/tests/library-convert.test.mjs`,
`roles/{companion,continuity,fact-checker,ideas,proofreader,research}.md`,
`packages/webui/src/{writing-http.mjs,library-http.mjs,cli.mjs,serve.mjs,bus.mjs}`,
@@ -1214,6 +1226,11 @@ bus restore. Test the third case explicitly: convert, then rollback --restore a
backup, losing the bus conversion event while archive and DOCX commits remain. The normal
counted-event approval/loss protocol applies; reconcile immutable commits/journal with new
recovery events before lookups resume. Never revive Markdown or replay authorization grants.
The fourth recovery case restores the whole host from a backup taken before an import,
losing its writing store and host import records as well as bus events. Rebuild the host
record from the import manifests on the writing store's off-host remote and the archive
commit in Gitea. Chapter lookups refuse until recovery proves one current version.
Restoring VM 1012 boots it, so Jason is present under Q185 (mos-1444 R14).
Old release capability preflight must refuse converted projects it cannot interpret.
Update the six creative .md role contracts' obsolete 'Every item cites the lines' text:
@@ -1222,8 +1239,9 @@ source format/scope from the map, archived never canon, and no story/conversion
still use only their approved lookups/message/supporting-write tools. Schema/role expansion
waits for the approved PRD, and existing role JSON never grants library.convert. Expose the
human CLI/Console conversion verb through runtime/processing.work and async readback, not
the agent tool list. Hard-wait tests are at business/tests/hard-waits.test.mjs:22; the new
action remains GATED_ONLY unless the approved PRD explicitly changes it.
the agent tool list. library.convert is absent from ACTIONS and ROUTED; host processing
never sends it to the broker. Role vocabulary validation already refuses an unknown action.
REQ-WAIT-1 and packages/business/tests/hard-waits.test.mjs remain unchanged (mos-1444 R6).
The importer lives at `packages/writing/src/import-markdown.mjs`, owns a reviewed AST mapping
from vendored markdown-it to the WordCraft DOCX model, and validates source token/metadata,
@@ -1249,6 +1267,7 @@ Independent conversion/parser/authorization review, writing/library/bus/harness/
pinned-copy and sandbox closure tests, plus W4 A19/A20/A33–A35 and G1. Source archive, off-host acknowledged DOCX and
format-map snapshot must match the new DOCX publication before completion. No real import
opens before all D12 gates. The October 27 Markdown and supporting-doc fixtures still pass.
The human opening step follows W8's landing (mos-1470, build order, not an acceptance condition).
### Handoffs
@@ -1256,6 +1275,83 @@ Owner sends commit/blob and fixture publication/crash evidence directly to the r
Sage through T3. Reviewer sends verdict to owner/Sage. Copy Mos for the later first-real-book
go or any proposed policy change, never ask an agent to execute that go.
## W8 Console Connections view for Markdown and DOCX story files
### Problem
PRD 1.17 REQ-LIB-5 at 30e6bb680, approved through Q239, requires the Console to list
current links and backlinks of every story file the user may see, in both formats, with
scope and format. Clickable editor wikilinks and Obsidian's historical archive graph do
not supply this view.
### Owner and reviewer
- Owner: Sage assigns the seat, using Codex gpt-6.1-sol for cross-format lookup and authorization.
- Reviewer: an independent Opus 5.5 seat assigned by Sage, not the author.
- After: W3, W4b and W7's reviewed closed conversion/format-routing candidate; serial handoff
from W7 for shared library/WebUI files and from C2-11a/11b and C2-10b for shared UI paths.
- Before: W7's human opening step for the first real chapter import, under mos-1470. This
orders builds, not REQ-LIB-8 acceptance, and does not change its four opening conditions.
Post-10-27 only, with no gate slot ahead of MVP rows.
### Files owned
New: `packages/library/src/connections.mjs`, `packages/library/tests/connections.test.mjs`,
`packages/webui/src/connections-http.mjs`,
`packages/webui/src/public/{writing-connections.js,writing-connections.css}`,
`packages/webui/tests/{writing-connections.test.mjs,writing-connections-browser.test.mjs}`.
Existing or handed off from W7/W3: `packages/library/src/{index.mjs,links.mjs}`,
`packages/webui/src/{serve.mjs,bus.mjs}`,
`packages/webui/src/public/{index.html,writing-pane.js,writing-pane.css}`,
`packages/webui/README.md`.
### What ships
A Console Connections control beside story navigation opens a keyboard-accessible list
with Links and Backlinks sections for the selected story. Each item shows its title,
scope and format, and opens the current authorized story through the existing resolver.
It works for Markdown, DOCX and a partly converted book. It uses committed link metadata
and W7's stable identities/format routing, never unsaved editor buffers or archive content.
The response names the captured versions; after a committed save or conversion publication,
a refresh uses the current authorized snapshot. An in-flight stale result cannot replace
that refresh. An empty section says there are no visible links; it does not reveal hidden
counts or titles. Unresolved links remain visibly unresolved and cannot open guessed paths.
A human-only Console read route uses existing authenticated project/universe/source access
and exclusions. Authorize both endpoints of every displayed edge before returning titles,
paths or counts. Backlinks cannot reveal another business, a denied universe, excluded
story or archived source. Links inside comments or tracked-deletion text are not current
story links, so the human Connections view never shows them and never depends on an agent
setting. Final-view story links use the same committed extraction rules as W4b. Archive links are historical
and excluded from this current-story view. No new agent grant, tool or conversation gate.
`connections.test.mjs` covers forward/backlinks across both formats, mixed-book identity
preservation through conversion, current committed saves, scope/format labels, exclusions,
archive/old-path refusal, unresolved targets and denial without metadata leaks. The HTTP/UI
suite in `writing-connections.test.mjs` covers authentication, access revocation, response
version binding, stale refresh rejection and refusal rendering. The browser fixture suite
covers keyboard/focus, Links/Backlinks empty states, labels, cross-format navigation and
refresh after save/publication. Fixtures only, never Melanie's text or a live stack.
### Out of scope
A node graph, Obsidian graph parity, editing links or story text, agent tools, private-book
conversion, changes to REQ-LIB-8 acceptance, Q234's header, mos-1363 or S0 M1–M14.
### Gate
Independent Opus 5.5 review of access filtering and the human view, the named fixture suites
and G1 on Sage's slot. Evidence shows both formats and mixed conversion state without
archive or denied-source leakage. Sage records W8 landing before W7's first-real-import
opening step per mos-1470; no import acceptance test or PRD condition is added here.
### Handoffs
Owner sends pushed commit/blob and fixture evidence directly to the reviewer and Sage via
T3. Reviewer sends verdict to owner and Sage. Sage orders W8 landing before the W7 human
opening step, and hands the shared files back before further W7 edits. Copy Mos for the
first-real-book go or any proposed policy change.
## Engineering choices from mos-1368
This table resolves the map's engineering prompts; these are design decisions, not questions
@@ -1273,7 +1369,7 @@ Later mos-1372/1375 override A32/A33's per-document/dedicated-session assumption
| Q7 / A32/A33 | mos-1372 D9: ordinary Case 2 access in any thread/room, no per-document grant/share/dedicated session; existing supporting writes continue; marked for PRD 1.17 approval |
| Q8 / A36 | W4b/W7: preserve Case 2 find/get/search/manuscript/universe tools via host committed-reader adapters, not fewer tools or raw ZIP access |
| Q9 / A73 / C2 | W7: D10 manifest order, typed next_chapter, paragraph cursors bound to snapshot/access; both W4 byte cap and harness REPLY_CAP apply, never cut paragraphs or invent line equivalents |
| Q10 / A22 | W3/W7 keep clickable cross-format editor links; no DOCX graph yet. Connections view/tests are later work, waiting on Jason's J-b answer; Obsidian archive graph is historical |
| Q10 / A22 | W3/W7 keep clickable cross-format editor links; W8 supplies current links/backlinks for visible story files in both formats with scope/format (REQ-LIB-5, Q239); no DOCX graph yet; Obsidian archive graph is historical |
| Q11 / A37 | W7: Console archive saves refuse; sync flags later archive edits or recreated old paths and excludes them from current canon; no remote-push refusal/protected-pattern guarantee |
| Q12 / A21 | W7: reviewed markdown-it AST + first-party DOCX adapter; no new runtime converter; input cap 1 MiB default, W4 ZIP/XML validation, construct/token/extractor fidelity before archiving |
| Q13 | W7 survey: synthetic construct corpus and aggregate-only count capability; no seat reads real text or claims a corpus census happened |
@@ -1308,7 +1404,9 @@ renderer does not enable it. No change is imposed on the October27 release.
Alignment note: sage-3121 checks this revision against PRD 1.17 d6,
SHA-256 `886b93c571f87bfbb2f27ee3113b6800a32d8f89196d95ace33566031bee380a`.
Connections graph/tests wait for J-b; new non-old-path Markdown stays canon until import;
That historical d6 alignment is superseded for Connections by landed PRD 1.17 at 30e6bb680:
REQ-LIB-5/Q239 require W8's list view, ordered before real import under mos-1470.
New non-old-path Markdown stays canon until import;
archive folder is human-set in the library mapping with no default.
## Current verification and remaining gates
@@ -1323,4 +1421,5 @@ append-only logs. No build, browser run, conversion, queue entry or live change
Jason's format and CSP choices are settled by Q234–Q237. No question repeats Q1 or Q36.
Before real conversion: Sage's Case 2 PRD amendment (Vikunja 570) needs Jason's approval;
W4b/W7, Hermes-retired and post-10-27 gates must pass; Jason must explicitly give the first real Melanie-book
conversion go. These are later authorization gates, not requests to convert anything now.
conversion go. W8 lands before the human opening step under mos-1470 as build order, not
an acceptance condition. These are later authorization gates, not requests to convert anything now.
+21 -7
View File
@@ -15,8 +15,9 @@ from the reviewed baseline except their replaced per-document-scope wording.
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
A story file in an unmarked folder inside a book or playground is eligible, and the human
selects one story file at a time. An unmapped story file converts only after the human maps
its folder. 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.
The October 27 release stays Markdown. Production import/new-story opening waits for W4b,
@@ -24,7 +25,9 @@ W7, the post-10-27 release, the approved Case 2 PRD/brief amendment, Hermes reti
(#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.
grant or agent request path. It is not in ACTIONS or ROUTED; the human host processing
adapter never sends it to the broker. A role file naming an action outside the vocabulary
is already refused. REQ-WAIT-1 and its hard-wait test remain unchanged (mos-1444 R6).
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.
@@ -63,7 +66,11 @@ A Markdown file recreated at a converted old path is unmapped, neither canon nor
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.
DOCX graph exists yet. PRD 1.17 REQ-LIB-5 and Q239 require the Console's Connections view
to list the current links and backlinks of each story file the user may see, in both
formats, with each one's scope and format. Parent design W8 owns the view and its tests.
Mos-1470 orders its landing before W7's first-real-import opening step as build order,
not an additional REQ-LIB-8 acceptance condition.
Every conversion runs the pinned extractor before archiving and refuses unsupported output.
An independently reviewed importer preserves every wiki token and front-matter key.
@@ -592,9 +599,10 @@ join stack-release COUNTED and T46 exactly once, so post-backup conversion event
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.
Three recovery cases are required: (1) crash after durable DOCX staging but before archive
Four 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
pre-conversion bus backup while archive commits and O2 documents remain, and (4) a whole-host
restore from a backup taken before an import. 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
@@ -606,6 +614,12 @@ pending operation to the human. Do not replay chapter text into an old index or
Incompatible old binaries cannot activate converted projects through the supported controller;
raw privileged rollback tampering is outside the guarantee. Never rewrite git/run history.
Case4 also loses the local writing store and host import records. Recovery rebuilds the
host record from the import manifests on the writing store's off-host remote and the
archive commit in Gitea. Chapter lookups refuse until it proves one current version;
missing or inconsistent remote evidence cannot revive old Markdown as current. Restoring
VM 1012 boots it, so Jason is present under Q185 (mos-1444 R14).
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
@@ -644,7 +658,7 @@ These are required future fixture gates, not implementation results.
| A24 | Full page and oversized paragraph | end before overflow; first oversized row refuses without text; no unreachable subrange claim |
| 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 |
| A27 | Conversion restore accounting and durable journal reconciliation | pre-conversion bus --restore or whole-host restore reconciles remote manifests and archive commit; lookups refuse until one current version is proven; 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 |
@@ -0,0 +1,61 @@
# Row 270's pi compaction reading moves behind the harness kind
Found during row 272's gate (#1777) on 10-23 (sage-3432). Mos ruled the move in mos-1455 item 3 and
declined an EXCEPT entry. Vikunja 598.
## Brief: row 270's pi compaction reading moves behind the harness kind (Vikunja 598)
Gitea #1814. Owner Gizmoduck, who wrote row 270. Reviewer Duckworth, who reviewed it. Gate owner Sage.
Base: refactor at this brief's commit. Design first, in `agents/gizmoduck/work/queue-307/DESIGN.md`.
`packages/plugins/tests/harness.test.mjs:192` (row 65) checks that no core source outside
`packages/plugins` names a harness. It is red on refactor 1e4c1e6e9. Its `named` list is three lines
of `packages/seat/src/compactions.mjs`:
- :232, where `readLedger` marks a ledger unsupported when a collection's harness isn't `"pi"`;
- :283 and :287, where `collectCompactions` branches on `harness === "pi"`.
Row 270 (#1775, 8c28ee731) added them. The 270+271 gate didn't run the plugins suite.
REQ-PLUG-2 (`docs/prd/mosaic-stack.md:1001`) sends new work that calls out to a part of a plug-in kind
through that kind from the start. Row 270 was new work, so its pi reading belongs in the pi harness
module, not in core.
Change:
- The harness kind (`packages/plugins/src/kinds.mjs:52`) gains an export that collects a session
store's compactions. A core change through `kinds.mjs` is in scope, as are its callers and its
contract in `packages/plugins/README.md`. The design names the export, its arguments, its answer,
and whether `interfaceVersion` moves.
- pi's module owns the stream reading (`scanPi`, `PI_VERSION`, the Claude-history check) and answers
what `collectCompactions` writes for pi today. claude-code's module answers harness-unsupported, as
it does now.
- Core keeps the ledger: `readLedger`, `compactionCount`, the O_APPEND write and the `collected` line.
No core line names a harness. `readLedger`'s unsupported test reads a field the module's answer
carries, not a harness name.
- `packages/cli/src/launcher.mjs:312` calls through the kind. `packages/bus/src/runtime.mjs:282` and
`broker.mjs` keep reading counts through `compactionCount`.
- A ledger that row 270's code wrote gives the same count after the move, including the pi,
claude-code and Claude-history cases. Nothing rewrites a ledger.
Tests, red first under mos-1272:
- a fixture ledger written by the current code reads the same count before and after the move, one
per reason row 270 writes;
- pi's module counts a fixture stream the way `scanPi` does now, with the read budget and stream cap;
- a harness module without the export refuses at load (`packages/plugins/src/loader.mjs:61-64`).
`harness.test.mjs` stays unchanged and goes green. Any change to an existing test goes to Mos through
Sage first. A new required export breaks every existing test fixture that builds a harness module, so
DESIGN.md lists those fixtures (grep the tests for `interfaceVersion` and the harness exports). Those
edits go to Mos with the design.
Mutants:
- core branches on a harness name again;
- pi's module drops the Claude-history check;
- `readLedger` treats every collection as supported.
Gate: Duckworth approves the design and then the manifest. On Sage's gate, the bus, cli, plugins,
webui and business suites run green, plugins with no failures at all, along with every
`scripts/test-*.sh`, and the mutants are killed.
+82
View File
@@ -0,0 +1,82 @@
# PRD 1.17 brief: Case 2 story files are DOCX
Sage, 2026-10-23, for mos-1366 item 1, mos-1467 and mos-1470. This follows
`docs/plans/BRIEF-TEMPLATE.md`. Gitea #1815, Vikunja 570.
## PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239)
### Problem
Jason's Q235 to Q237 make Case 2 story files DOCX, with Markdown kept for notes
and docs. PRD 1.16 still says every story file is Markdown in the user's
repository, seen in Obsidian. The 1.17 text is approved: Jason approved d8 at
435a7458d in Q239 with O2, which adds the Console Connections view (mark J-b).
d9 carries that view in Mos's mos-1443 R3 text. Webby approved d9 in rounds 7
and 8 (webby-0214, webby-0217), and Mos passed the J-b check in mos-1470.
The approved text is `agents/sage/work/prd-1-17/DRAFT-d9.md` at 4607e15b8, blob
sha256 02f28decad7f946f. Its base is PRD 1.16, `docs/prd/mosaic-stack.md` blob
a6db9b4a at 482e865ac (row 291).
### Owner and reviewer
- Owner: Gosalyn.
- Reviewer: Webby.
- After: row 291 (PRD 1.16).
### Files owned
The four files in DRAFT-d9.md's "Files the row would own", as it lists them:
- `docs/prd/mosaic-stack.md`.
- `packages/tasks/requirements.json`: version 1.17, the titles of REQ-LIB-1,
REQ-LIB-2 and REQ-LIB-4, and the new ids REQ-LIB-8 and REQ-LIB-9 after
REQ-LIB-7 and before REQ-INST-1.
- `packages/tasks/tests/requirements.test.mjs`: the version pin 1.17, the count
87 (:21), and the duplicate-id message `requirements[87].id: REQ-ROLE-1 twice`
(:77).
- `packages/bus/tests/requirements.test.mjs`: the list length 87 (:48).
### What ships
- Every "Replace", "Add, after", "becomes" and "New" instruction in DRAFT-d9.md's
PRD sections, applied to the 1.16 text word for word. Each quoted old text
occurs once in a6db9b4a, except "The Console shows each cite's scope.", which
d9 scopes to REQ-LIB-2. Webby checked those counts in round 6.
- The 1.17 header, Status line, source lines and notes as d9 writes them.
- requirements.json as d9's requirements.json note says: REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 take the new titles, REQ-LIB-5 and REQ-LIB-6 keep theirs, and
REQ-LIB-8 and REQ-LIB-9 are added with the titles d9 gives, so the count is 87.
REQ-LIB-4's title is 117 characters, under the 120 limit in `requirements.mjs`
(mos-1443 R2).
- Nothing from d9's review sections goes into the PRD. That covers "Settled
points", the "d2 changes" through "d9 changes" lists, "What d5 adds", "Marked
for Jason" and the impact map notes.
### Out of scope
- The build rows for Vikunja 570 and 571, which Sage files after the landing.
- The queue changes in the impact map (A24, A57 to A59), which go through
`scripts/mosaic queue` (C9).
- Gyro's design edit R3.4 at 767bb62c7.
- Mos's build-order call (mos-1470): the Connections view lands before the first
real chapter import opens. That belongs to Gyro's 571 rows, not the PRD.
### Gate
- Webby checks the candidate's PRD diff against DRAFT-d9.md, one instruction at a
time, and checks that nothing else changed.
- No new Jason question. Q239 approved the text, and a change beyond d9 would
need a new version and his approval.
- On Sage's gate:
- tasks, run whole;
- bus, run whole, because the broker loads requirements.json;
- cli `stack-copy.test.mjs`;
- every `scripts/test-*.sh`, under `timeout -k 10`.
- Mos gets the landing commit (mos-1470).
### Handoffs
Every seat hands off directly (DECISIONS Q210, mos-1221). When the owner pushes a
round or the reviewer posts a verdict, that seat messages the next seat's T3
thread and Sage's with `mcp__t3__t3_send_message`. Mos has one step here, which
is receiving the landing commit.
+34
View File
@@ -644,3 +644,37 @@ Open follow-up (dewey-0913 N1). Row 219 (#1720, C2-09a) lands on 5fdfd7275 with
## Row295 N1: a revoked run's late bus call exits 22, not stopped
Open follow-up (morgana-0167, sage-3318, sage-3360), to become a row. Row 295 (#1801) lands on 2efd127ce. Revocation ends a user's thread claim in one transaction, and the launcher then sends SIGTERM. A run that calls the bus between the claim end and the SIGTERM gets not-holder and exits 22, so its record reads error rather than stopped. Row 302's launcher-released answer covers only threadIdle and landRelease. The row gives a revoked claim the same shape: calls that need the claim answer thread-revoked until the run ends, and the runner maps that answer to stopped. It needs a forcing test that holds the window open and a mutant the test kills. Brief: docs/plans/2026-10-23_gate-env-rows.md; owner Morgana, reviewer Dewey, after row 272.
## Row272 N1: users can't reach session verbs
Open follow-up (Huey's BUILD.md, sage-3357), to become a row. Row 272 (#1777) lands the session CLI and the Console chooser for owners. USER_VERBS still leaves out the session verbs, and no transport carries a user's session operation. A granted user's Console therefore falls back to the thread default when session.list answers access-refused on a route with no session named. A session link with an explicit session still refuses, and the session controls stay hidden. The row that adds user session grants (193/T5) adds the verbs to USER_VERBS under a grant check, and adds a test that a granted user lists, opens and clears only sessions on threads the grant covers. It removes the fallback once the grant exists.
## Row270 N6 rebound: the ledger and the session.list read are still unbounded after 272
Row 272 (#1777) lands without bounding the compaction ledger or the whole-file read on session.list, and it records no reason the 64 MiB collection cap is enough. N6 above stays open. It moves to the row after 307 (#1814, Vikunja 598), because 307 reshapes the compaction reading in packages/seat/src/compactions.mjs and both rows would edit readLedger. That row bounds the ledger or the read, with a test and a mutant, or records why the cap is enough.
## Row307 N1: row 270's store-unreadable doesn't stick
Duckworth, row 307 build round 1 (duckworth-0285, note d43690d0b). When a collection finds an unreadable
session store header, it writes store-unreadable. The cursor then moves past the bad header, so the next
collection never checks it again and the count reads as known with 0. The row 307 ledger test pins this
behavior as it stands, so the move didn't change it. Bound to a follow-up row on row 270's
store-unreadable path. The fix keeps the unreadable state until the header reads clean, or re-checks the
header on every collection. Either way it's an old-test change and goes to Mos.
## Row306 N1: the revoke Set doesn't survive a broker restart
Morgana, row 306 BUILD.md (#1813, landed 2d0318a44; Dewey agrees, dewey-0918). `#accessRevoked` lives in
memory. After a broker restart, recover() rebinds an earlier host's run, and if that run makes a bus call
before the launcher's SIGTERM, it answers `not-holder` and the run exits 22, as every revoked run did before
306. Seeding the Set at a rebind needs a mark only `#revokeLost` writes; `reason` and `by` on the ended
claim aren't one (design D1's alternative). Bound to the next row that changes the broker's recover() path.
## Row306 N2: a pending limit stop answers before the revoke
Morgana, row 306 BUILD.md (#1813, landed 2d0318a44). A revoked run that also has a pending limit stop gets
`#limitStop`'s `not-holder` on `message.receive` and `state`, because that check runs before `#agent`. It's
rare and the run still stops. Bound to the next row that changes `#limitStop` or the order of those checks.
+22 -7
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 3510 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
Generated from `docs/plans/queue.json` revision 3636 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|---|---|---|---|---|---|---|---|---|
@@ -288,7 +288,7 @@ Generated from `docs/plans/queue.json` revision 3510 by `queue render`. Do not e
| 249 | row 235 follow-up: the grant undo records hostLineLeft when install never returned, since the remote add can outlive an abort or timeout (mos-1166) | gizmoduck; reviewers duckworth | #1751 | done | — | Duckworth approves the manifest. On Sage's gate the bus suite and the whole touched test files are green, and a mutant that drops the new catch branch is killed. (sage) | `docs/plans/2026-10-20_host-credentials.md` § Machine grant row b follow-up: the undo can't prove the remote add has ended | LANDED 9de5a57ab on refactor, pushed with 7b9737953 (DEFERRED N1). Range-diff: 8a04f4aa4..05595794b vs 8ca064158..9de5a57ab, same 3 paths, patch-id 99551b122e66 on both. Gate sage-2026 14:53Z: grant-executor whole 39/0, bus 598/0, no stub left, C1 (drop :1025) killed by G8 and both G23. Duckworth approve c30070. |
| 250 | K12 (cohort.test.mjs) reads cgroup.events after cgroup.kill under load: print the shim's reason, then fix the cause at the source (Vikunja #450) | gizmoduck; reviewers filbert | #1755 | done | 197 done | reviewer approves the manifest; conversation and control-board suites whole on Sage's slot (sage) | `docs/plans/2026-10-21_k12-cohort-events.md` § Brief: K12 reads cgroup.events after cgroup.kill under load | LANDED 1b6158ce2 on refactor 19:42Z: candidate c1a858975 on 3b0aef995, manifest eca0b8e2, Filbert APPROVE rev 2957 (#1755 c30130). patch-id 29786caa = candidate; blob diff empty; tree 59f1e4781 = merge-tree. Gate on Sage slot sage-2109 (gizmoduck-0150): P1/P2/M1 as predicted, conversation whole 3x 183/183 plus concurrent pair, control-board 124/124. No drift on paths since base. |
| 251 | T27 (host-run.test.mjs:453) lists no identities right after ssh-add -t 1: print timing and agent state, then fix the cause at the source (Vikunja #453) | darkwing; reviewers dewey | #1756 | done | 197 done | reviewer approves the manifest; bus suite whole on Sage's slot (sage) | `docs/plans/2026-10-21_t27-ssh-add-lifetime.md` § Brief: T27 lists no identities right after ssh-add -t 1 | LANDED d37210288 on b79ad5f2c: candidate 27f01ef63 manifest f2bdd684, patch-id 440d2442c976 equal, blob diff empty, tree matches merge-tree. Dewey r2 approve rev 2996 (c30168). Gate: bus suite whole in a clean worktree, 627/627 at 21:30:33Z on Sage's slot. Overlap check vs 194/198/244/253/254 branches: none. DEFERRED sleep-orphan entry follows as a doc commit. |
| 252 | Console inbox Approve and Deny for grant requests (#435, J2) | scrooge; reviewers webby | — | in-progress | — | Webby approves DESIGN.md then manifest; webui, bus, cli suites and every test-*.sh green on Sage's gate; DESIGN mutants killed; REQ-WEB-2 PRD amendment landed first (sage) | `docs/plans/2026-10-21_console-inbox-approve.md` § Gate | mos-1193. Chained on row 196's candidate (Q177): design starts now on Gander's row 196 build; code rebases onto 196's landing. Vikunja #435. |
| 252 | Console inbox Approve and Deny for grant requests (#435, J2) | scrooge; reviewers webby | #1753 | done | — | Webby approves DESIGN.md then manifest; webui, bus, cli suites and every test-*.sh green on Sage's gate; DESIGN mutants killed; REQ-WEB-2 PRD amendment landed first (sage) | `docs/plans/2026-10-21_console-inbox-approve.md` § Gate | Landed 45ea3fa0d (replay of Scrooge 580037105 onto 30e6bb680, range-diff all =, 10 commits). Webby APPROVE c31229 rev 3612. Sage gate on 45ea3fa0d: every package suite 0 fail 0 skipped (bus 907, cli 943, webui 410), every scripts/test-*.sh rc 0, J1-J19 all killed except J11c and J18 (equivalent per BUILD.md), all restores match. Old-test numstat 1 1 vs fe4ee8c7b held. |
| 253 | F27: case2-unit.sh and case1-accept.sh move to --tester-socket and refuse --tester-port with stack-release's migration wording; no .53/.54 copy with 196 until this lands (mos-1198 item 4) | gander; reviewers duckworth | #1758 | done | 196 done | reviewer approves DESIGN.md then the manifest; cli and webui suites whole and every scripts/test-*.sh on Sage's slot; mutants per DESIGN.md; static check covers scripts and tests (sage) | `docs/plans/2026-10-21_tester-socket-scripts.md` § Brief: case2-unit.sh and case1-accept.sh move to --tester-socket (F27) | Landed 48e450ce8 on refactor (fast-forward from aca16404c, 22 commits; landed sha = candidate, so range-diff and patch-id are identical by construction). Manifest 591020a1 (7 paths) rechecked against the commit: 0 mismatches. Gate: run 5 whole, cli 55 files, webui 58 files, all 10 scripts/test-*.sh green; M1-M40 killed 40/40. Duckworth APPROVE round 1, Gitea #1758 comment 30696. |
| 254 | Library sync follow-up: client timeout, STATUS codes, README (222 deferrals, #1759) | megavolt; reviewers dewey | #1759 | done | 196 done | Dewey approves the manifest; bus, library and webui suites whole and every scripts/test-*.sh green on Sage's slot; the timeout and STATUS mutants killed (sage) | `docs/plans/2026-10-21_library-sync-followup.md` § Gate | Landed 331597709 (candidate b31300d79 blobs and modes on 41b0cf755; patch-id 96216216 equal, range-diff differs only in the message) plus doc eb9672c91 (Dewey N1 README sentence closed; N2 four 502 codes to DEFERRED, binding the Console library routes row). Merged-tree rerun on Sage's slot 23:44-23:55Z: library 77/0, bus 628/0, webui 305/0 with 1 skip, cli 749/0. Megavolt's gate megavolt-0102 covers scripts and both mutants. |
| 255 | Bundled Pocket ID for users with no OIDC provider, design (Q203, #1760) | launchpad; reviewers webby | — | queued | 252 done | Webby approves DESIGN.md; design only, build rows follow after Sage checks it against Q203, O6, REQ-WEB-2 and REQ-INST-1 (sage) | `docs/plans/2026-10-21_bundled-pocket-id.md` § Gate | Vikunja 458, Gitea #1760, mos-1202 item 2. REQ-INST-2 lands in PRD 1.10 as post-v1 (mos-1204). Design only: no install, no download, no .53/.54, dragon-lin keeps Authentik. |
@@ -308,7 +308,7 @@ Generated from `docs/plans/queue.json` revision 3510 by `queue render`. Do not e
| 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 | 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-progress | 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 | — |
| 272 | 264e T1 item 7: mosaic session CLI and Console chooser | huey; reviewers scrooge | #1777 | 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 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 | LANDED d33989230: 5 row commits replayed onto 1e629c892, every non-log patch-id equal, candidate paths = bea0c8e1c. Scrooge APPROVE rev 3579 c31184. Sage round 4 gate: bus, cli, plugins, webui, business; plugins red only the known base red harness.test.mjs:192 on compactions.mjs:232/283/287 (row 270), allowed by mos-1456, fixed under row 307 Vikunja 598; M1-M6 and F1-F4 killed. DEFERRED: 272 N1 user session verbs, 270 N6 rebound. |
| 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. |
@@ -327,7 +327,7 @@ Generated from `docs/plans/queue.json` revision 3510 by `queue render`. Do not e
| 288 | Case 2 C2-02 agent writes, the notice, revert and her Console saves | honker; reviewers webby | #1793 | briefed | 215 done, 282 done, 283 done, 284 done, 285 done, 286 done | Webby approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate, mutants killed. A search of the run output for the fixture tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-02 agent writes, the notice, revert and her Console saves | — |
| 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) | — |
| 291 | PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | gosalyn; reviewers webby | #1796 | done | 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) | Landed 4874d1e83 on e909ef51a: 482e865ac replayed, patch-id equal (cef7de74), range-diff =. Webby APPROVE c30848 on 482e865ac; Jason approved the text in Q229. Gate 16:15Z on the tip tree: every package suite but queue, 0 fail 0 skipped (tasks 100, bus 891, cli 941, plugins 119, webui 392, business 231), every scripts/test-*.sh rc 0 (queue-root and row 244 skips only). PRD 1.16. 1.17 is row 308. |
| 292 | stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | gizmoduck; reviewers duckworth | #1797 | done | 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) | Landed 6aab979e6 (cherry-pick of c902a767d..90be6cccd, range-diff all =, patch-id a45c9ba4 equal). Tested head 0d0021454: stack-release.test 224/224, cli suite r2 red only compactions-launch:229 session-invalid 22 (idle-stop race, Vikunja 588, row 302), r3 rerun 910/910 under mos-1411, ten shells green, M1-M9 killed. Duckworth APPROVE rev 3433 on 2fa7ae17. |
| 293 | 574: maintenance-free test fixture commits (Vikunja 574) | gander; reviewers filbert | #1805 | done | — | 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) | LANDED e51d11640 03:55Z 10-23 (cherry-pick of gander b23783f39..a9cae2bd6 onto 464010fbe; range-diff 6/6 equal, range patch-id equal). Filbert APPROVE c30982 cdda41f5; run 2 4 files 153/0, seat 202/0, cli 905/0, 312 flagged/0 maintenance children. N1: lifecycle.test needs TMPDIR of 50 bytes or less. Residual: queue store.mjs, discord git.mjs, harness tools.mjs unflagged. #1805 closed c31013, P19 574 c888. |
| 294 | FED R6: Console granted-user sign-in, user-cli transport, peer links | launchpad; reviewers duckworth | #1800 | done | — | 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 | landed d3059c2c3 on refactor 07:04Z: cherry-pick of c902a767d..24b745447 onto 09d0d6faf, range-diff 11 =, patch-id 8371405a equal. Duckworth approve c31053 rev 3469 on a1ff5d67, C1/C2 met. Run 4 on 8616b9a4f from npm ci: webui 365, cli 909, bus 873, 10 scripts, M1-M11 killed. Merged tree: stack-release-external 25, stack-upgrade 21, stack-release 223/224 (R9 transient, row 1810) then 224/224 rerun under mos-1422. Follow-ups rows 300, 301. Closes #1800. |
@@ -336,10 +336,25 @@ Generated from `docs/plans/queue.json` revision 3510 by `queue render`. Do not e
| 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 | done | 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) | landed ffb2fedfb 08:03Z on 15e688cc0 via Filbert SESSIONS c1f910b4c; 5 commits = gander/row299-gate-277ace0b 63db96d8b (range-diff 5/5 =, tree differs only BUILD-LOG/SESSIONS); manifest 277ace0b sha256 -c OK. Gate green on ffa93b054: case1 whole 3x 53/0/0, cli whole 917/0/0 (59 files), P10 inner 2.27 outer 4.01, M1-M6 killed (gander-0189). Filbert approve rev 3485 c31084; N1 M1 1.23 s margin. |
| 300 | R6-N1: HTTP-level test of the user-session option swap (294 N1) | launchpad; reviewers duckworth | #1807 | briefed | 294 done | Duckworth approves the manifest; new test file whole, webui suite, mutant M12 killed (sage) | `docs/plans/2026-10-23_r6-followup-rows.md` § Brief: R6-N1, an HTTP-level test of the user-session option swap | — |
| 301 | R6-N4: fold user-bus.mjs back into bus.mjs (294 N4) | launchpad; reviewers duckworth | #1808 | briefed | 294 done | Duckworth approves the manifest; channel-proof.test, webui suite and 294 user-transport tests whole and green; re-pin only with Mos go (sage) | `docs/plans/2026-10-23_r6-followup-rows.md` § Brief: R6-N4, fold user-bus.mjs back into bus.mjs | — |
| 300 | R6-N1: HTTP-level test of the user-session option swap (294 N1) | launchpad; reviewers duckworth | #1807 | done | 294 done | Duckworth approves the manifest; new test file whole, webui suite, mutant M12 killed (sage) | `docs/plans/2026-10-23_r6-followup-rows.md` § Brief: R6-N1, an HTTP-level test of the user-session option swap | Landed on refactor as 9d929911c..07315e651 (from launchpad/row300 a00b25b37, range-diff equal), pushed 5b846b048 12:28Z. Duckworth approved r2 (c31158, rev 3559). Sage gate on bcf3c1dac: user-option-swap 1/0, webui 375/0 with pinned esbuild and MOSAIC_REQUIRE_EDITOR_BUILD=1, M12 killed at :151. Run 1 red placed under mos-1272 (headers). N1 deferred: board proxy upstream has no recorder. |
| 301 | R6-N4: fold user-bus.mjs back into bus.mjs (294 N4) | launchpad; reviewers duckworth | #1808 | done | 294 done | Duckworth approves the manifest; channel-proof.test, webui suite and 294 user-transport tests whole and green; re-pin only with Mos go (sage) | `docs/plans/2026-10-23_r6-followup-rows.md` § Brief: R6-N4, fold user-bus.mjs back into bus.mjs | Landed on refactor as bb031ec84..5b846b048 (from launchpad/row301 acc97fb9c, range-diff equal), pushed 12:28Z. Re-pin of webui bus.mjs to 39fcb27c is its own commit 59f0e44f4 (mos-1449); fixture stubs per mos-1451. Duckworth approved r2 (c31159, rev 3560). Sage gate: channel-proof 2/0, webui 374/0 pinned esbuild, granted-user 19/0. Live unchanged; 301 makes its own re-pin window. |
| 302 | Idle-stop race: an idle-released run's last call ends it session-invalid/22 (Vikunja 588) | gizmoduck; reviewers dewey | #1809 | done | 292 done | Dewey approves the design, then the manifest; forcing test, compactions-launch.test whole, bus/cli/harness suites, all scripts/test-*.sh; fix-drop mutant dies (sage) | `docs/plans/2026-10-23_idle-stop-race-row.md` § Brief: the idle-stop race (Vikunja 588) | Landed 3ed9fd754 08:49Z 10-23: Sage cherry-picked 0e30a85fa..2a8281a6f (7 commits) onto 1ac655005; range-diff all equal, patch-ids match, tree equals gated merge 783602e06 plus packet-only 2a8281a6f files. Gate run 2 green (bus 876, harness 151, cli 918, ten test-*.sh, M1-M5 M7-M9 killed). Dewey approve rev 3492 c31092. mos-1430 fixed launcher-released.test:89. mos-1411 rerun allowance ends. |
| 303 | R9 shows its output when rehearse makes no directory (Vikunja 589) | launchpad; reviewers duckworth | #1810 | in-review, round 1, request posted | 294 done | Duckworth approves the diff; stack-release.test.mjs whole and green on Sage's gate, in a gap (sage) | `docs/plans/2026-10-23_r9-guard-row.md` § Brief: R9 shows its output when rehearse makes no directory (Vikunja 589) | — |
| 303 | R9 shows its output when rehearse makes no directory (Vikunja 589) | launchpad; reviewers duckworth | #1810 | done | 294 done | Duckworth approves the diff; stack-release.test.mjs whole and green on Sage's gate, in a gap (sage) | `docs/plans/2026-10-23_r9-guard-row.md` § Brief: R9 shows its output when rehearse makes no directory (Vikunja 589) | Landed e551cd846 from launchpad 88ffecaf7, patch-id 559c802b equal, test blob e936808b matches the manifest. Gate: Duckworth approve c31079; Sage gate on c13c337e1 plus the test blob, stack-release.test.mjs whole 224/0 rc 0 in 595 s, alnum mktemp TMPDIR, default networks 6 before and 6 after. No overlap with in-review 272, 304, 305. |
| 304 | claude-session test builds the memory slug the way the CLI does (Vikunja 590) | rocko; reviewers dewey | #1811 | done | — | Dewey approves the diff; claude-session.test.mjs whole and green 3x (mktemp, underscore, space TMPDIRs) on Sage's gate, in a gap; old-regex mutant red at :298 (sage) | `docs/plans/2026-10-23_gate-env-rows.md` § Brief: claude-session test builds the memory slug the way the CLI does (Vikunja 590) | Landed a863015a5 from rocko 5e8354ebb, range-diff equal, patch-id 808fa765. Gate: Dewey approve c31129 rev 3533; Sage gate on e551cd846 with file 7690a4a9, claude 2.1.309: claude-session.test.mjs whole 10/0 under mktemp, underscore and space TMPDIRs; old-regex mutant under underscore 9/1 at :298. Gates may drop the alnum TMPDIR rule (mos-1432 ends here). |
| 305 | Shell suites remove the compose network they create (Vikunja 591) | gizmoduck; reviewers filbert | #1812 | done | — | Filbert approves the diff; test-release.sh and test-task.sh whole and green on Sage's gate, in a gap, project network absent after each run; dropped-rm mutant red (sage) | `docs/plans/2026-10-23_gate-env-rows.md` § Brief: shell suites remove the compose network they create (Vikunja 591) | Landed 353d65f57..d52d9f1f2 from gizmoduck 541dee2b7..7e8218a2e, range-diff all equal, on top of filbert 2c289f6f0. Gate: Filbert approve c31127 rev 3534; Sage gate on e551cd846 with task 7d6fbf67, release b29850ce: test-task 92/0, test-release 15/0, network check OK; mutants a and b red in both, c a known survivor; own mutant networks removed, none left. Gates stop the manual network rm (mos-1438 ends here). |
| 306 | A revoked run's late bus call stops it instead of exiting 22 (295 N1) | morgana; reviewers dewey | #1813 | done | 272 done | Dewey approves the design, then the manifest; bus, harness and cli suites and every scripts/test-*.sh green on Sage's gate (sage) | `docs/plans/2026-10-23_gate-env-rows.md` § Brief: a revoked run's late bus call stops it instead of exiting 22 (295 N1) | Landed 2d0318a44 on 4874d1e83: 33d3844a0..683833205 replayed, 4 patch-ids equal (a1960f36 55fadbc3 af1a3d88 35cf81a3), range-diff all =. Dewey APPROVE c31206 rev 3596 on 683833205. access-revoke.test numstat 7 7 (mos-1463). Sage gate 16:33Z: every package suite 0 fail 0 skipped (bus 894, cli 942, plugins 119, webui 392, business 231, harness 151), every scripts/test-*.sh rc 0, M1-M3 each killed on every named file, restores equal. N1/N2 to DEFERRED next. |
| 307 | Row 270's pi compaction reading moves behind the harness kind (REQ-PLUG-2, Vikunja 598) | gizmoduck; reviewers duckworth | #1814 | done | — | Duckworth approves the design, then the manifest; bus, cli, plugins (0 failures), webui and business suites, every scripts/test-*.sh green and the mutants killed on Sage's gate (sage) | `docs/plans/2026-10-23_harness-kind-compactions.md` § Brief: row 270's pi compaction reading moves behind the harness kind (Vikunja 598) | LANDED d243f6b25: 6 row commits replayed onto 8dc132bec, every non-log patch-id equal, candidate paths = 6fdfe3790 (manifest 17c9f8d9). Duckworth APPROVE rev 3586 c31189 round 1, C2 met. Sage gate: bus, cli 0 skipped, plugins fully green incl harness.test.mjs:192, webui, business, seat, every scripts/test-*.sh; M1-M4 killed. P19 598 commented. DEFERRED: 307 N1 store-unreadable not sticky. N3 doc-only BUILD.md red list one short, next BUILD.md change. |
| 308 | PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239) | gosalyn; reviewers webby | #1815 | done | 291 done | Webby checks the PRD diff against DRAFT-d9.md at 4607e15b8 one instruction at a time and that nothing else changed. No new Jason question (Q239 approved the text). tasks whole, bus whole, cli stack-copy.test and every scripts/test-*.sh green on Sage's gate. Mos gets the landing commit (mos-1470). (sage) | `docs/plans/2026-10-23_prd-1-17-brief.md` § PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239) | Landed 30e6bb680: Gosalyn 45fe7b3ce replayed onto 7defb58e9, patch-id 4658c831 equal, range-diff =, four blobs equal. Webby r1 APPROVE c31224 rev 3606, d9 at 4607e15b8 applied word for word. Sage gate 17:19Z: every package suite 0 fail 0 skipped (tasks 100, bus 894, cli 942, plugins 119, webui 392), every scripts/test-*.sh rc 0. Mos gets the sha (mos-1470). 570/571 build rows next. |
| 309 | 571 W1: reviewed local thesaurus data (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W1 reviewed local thesaurus data | — |
| 310 | 571 W2: WordCraft source import and reproducible restricted web build (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W2 WordCraft source import and reproducible restricted web build | — |
| 311 | 571 W3a: bus pins and human-only editor settings (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3a bus pins and human-only editor settings | — |
| 312 | 571 W3b: human DOCX store and instant commits (post-10-27) | unassigned | — | queued | 311 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3b human DOCX store and instant commits | — |
| 313 | 571 W6: owned browser bridge and editor-only route (post-10-27) | unassigned | — | queued | 309 done, 310 done, 312 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W6 owned browser bridge and editor-only route | — |
| 314 | 571 W3: full editor pane, navigation, pins and right-click synonyms (post-10-27) | unassigned | — | queued | 311 done, 312 done, 313 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3 full editor pane, navigation, pins and right-click synonyms | — |
| 315 | 571 W4b: committed reader, citation resolver and typed tools (post-10-27) | unassigned | — | queued | 314 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W4b committed reader, citation resolver and typed tools | — |
| 316 | 571 W5: read-only flags, citations and chat suggestions (post-10-27) | unassigned | — | queued | 315 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W5 read-only flags, citations and chat suggestions | — |
| 317 | 571 W7: human one-way chapter import and Case 2 format routing, production import closed; the first real import opens only after W8 lands (post-10-27, mos-1470) | unassigned | — | queued | 314 done, 315 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W7 human one-way chapter import and Case 2 format routing | — |
| 318 | 571 W8: Console Connections view for Markdown and DOCX story files, before W7's first real import (post-10-27, mos-1470) | unassigned | — | queued | 314 done, 315 done, 317 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W8 Console Connections view for Markdown and DOCX story files | fenton-0017 carry: connections.test.mjs must test that links inside comments and tracked deletions are excluded. W8 follows W7's closed routing candidate and lands before W7's first real import opens (mos-1470) |
<!-- mosaic-queue:end -->
+4419 -43
View File
File diff suppressed because it is too large. Load diff
+399 -44
View File
@@ -1,6 +1,6 @@
# PRD: Mosaic Stack
- Status: approved, version 1.15. Jason approved draft 0.4 unchanged as
- Status: approved, version 1.17. Jason approved draft 0.4 unchanged as
1.0 on 2026-10-05 (lead decision 61). They approved version 1.1, the
Organization requirements, in decision 03130d08-ddce-480d-a601-e59f64744fc3
on 2026-10-14. They approved version 1.2, the chief of staff, the
@@ -44,7 +44,44 @@
as 1.11 (mos-1233, mos-1238, mos-1241, vikunja:19/464). Version 1.15
rewrites REQ-LIB-6 to Jason's Q223 and Q226 rulings and adds supporting
folders to REQ-LIB-1 and REQ-LIB-2. Mos ordered it in mos-1320 item 8,
and Jason gets a report.
and Jason gets a report. Version 1.16 routes the new action
`library.write` under REQ-LIB-6 (REQ-WAIT-3). A change to the hard waits
needs Jason's approval under REQ-WAIT-2, so Mos puts the text to him as
written, and it lands only after his yes (mos-1328, mos-1329). Version
1.17 records Jason's rulings Q235 to Q237 of 2026-10-23: Case 2 story
files are DOCX, Markdown stays for agent files and documentation, and a
human-started import converts a Markdown story file one way. It also
adds REQ-LIB-9, the Console's DOCX editor, from Jason's rulings Q195,
Q226 and Q234. It changes what Case 2 requirements allow, so Mos puts
the text to Jason as written, and it lands only after his yes (mos-1366,
Vikunja 570).
- Version 1.17 source: Jason's Q235, answering Q36: 'How about we see a
markdown file, we convert the markdown file to docx, then archive or
mark the markdown file as converted. The new standard for files in the
creating writing use case is to use docx to handle versioning and
WYSIWYG. We eliminate the potential for confusion based on the files.
Agents must understand which files are what format and where they live.
The system must allow for import and conversion. We will not run the
systems in parallel. There will be an irreversible conversion of the
files.' His Q236 says: 'For creative writing chapters, we need to
utilize docx as the standard. Markdown is acceptable for agent files,
instructions, PRDs, documentation, etc. The creative writing files
should be docx for familiarity for the user.' His Q237 says 'Q38: It is
correct for now (subject to future change)', which confirms Mos's
reading P1 to P6. REQ-LIB-9 rests on his Q195 request for 'a full
WYSIWYG editor within Mosaic Stack', his Q226 answer 'F41: We want to
move editing into the WYSIWYG in the WebUI. All changes are instantly
committed and available to the agents.' and his Q234 answer 'Q37: yes'
to the exact security header for the editor's frame. The storage follows
the O2 design D10 to D12 as rewritten under Vikunja 571, at b99ca86b9 on
`refactor`.
- Version 1.16 source: Webby's review webby-0164 B2, from Scrooge's row
285. Version 1.15 lets creative agents write in supporting folders, but
the write action stayed a hard wait under REQ-WAIT-3, and REQ-WAIT-2
refuses every role file that grants one. Mos ruled in mos-1328 and
mos-1329 that the routing is its own version. The bounds come from
REQ-LIB-6 and the Case 2 design's ruling after r11, f6e3e7f20 on
`gosalyn/case2-design`.
- Version 1.15 source: Jason's Q223, answering Q29: "The agents are not
allowed to write the prose into the writing projects. Agents are
allowed to CRUD character sheets, info summaries, cross-link info, and
@@ -706,6 +743,14 @@ Added in version 1.5 (Q167).
`host.sudo` was added as a hard wait and routes under REQ-CRED-4 since
version 1.8.
`library.write` was added as a hard wait and routes under REQ-LIB-6
since version 1.16. It is granted within-role to the six creative roles
only, and only for Markdown files under the supporting folders a human
marks. It never writes a story file. A write also needs the writing
agent's own live Gitea grant on the library project. Each write is one
commit by that agent's own Gitea account with a trailer naming the
agent, and the user sees a notice for it and can revert it.
### Messages
- **REQ-MSG-1.** Messages are addressed to a role. They are append-only
@@ -1178,7 +1223,8 @@ design `docs/plans/2026-10-21_t1-sessions.md`, 2c843d190 on `huey/t1-sessions-de
Added in version 1.12 (Q176 Case 2, Q211). Drafted from the Case 2
design r11, fb20a61b4 on `gosalyn/case2-design`, approved by Webby in
82f50d127, with the rulings recorded after r11 up to 624212842. The
library is the user's own writing repository.
library is the user's own writing repository plus the stack's writing
store, which holds the DOCX story files (REQ-LIB-8, version 1.17).
- **REQ-LIB-1.** A library project maps the user's writing repository
onto universes, books, entity types such as character and place, the
@@ -1187,9 +1233,12 @@ library is the user's own writing repository.
human changes it, and an agent's organization write that changes it
is refused. So
`universe.book.{character,place,...}` reads as universe, book folder,
type folder. (Design 1.1 and M9. Q176 Case 2 "Data is stored in
little-endian organizational scheme". Version 1.15: supporting
folders.)
type folder. The mapping also names one archive folder, set by the
human, where converted Markdown story files go. A book's DOCX story
files are in the stack's writing store, under the same universe and book
(REQ-LIB-8). (Design 1.1 and M9. Q176 Case 2 "Data is stored in
little-endian organizational scheme". Version 1.15: supporting folders.
Version 1.17: the archive folder and the writing store.)
- **REQ-LIB-2.** Playgrounds are never confused with canon. A playground
has one of three levels, set by where its prefix sits. A book-level
prefix lies inside one of its universe's book folders, and its files
@@ -1204,18 +1253,34 @@ library is the user's own writing repository.
its own universe, when two prefixes overlap, when a draft has books or
no prefix, and when a hybrid names a draft as a source. Supporting
prefixes, the folders agents may write in (REQ-LIB-6), follow the same
placement rules. A file's scope comes from its path alone, prefixes
first: a Markdown file under a supporting prefix is supporting, one
under a playground prefix at any level is playground, any other
Markdown file in a book folder is canon, and everything else is
unmapped and never returned. A folder in both lists is supporting.
Lookups return canon unless the caller asks for playground or
supporting. A citation marked as canon must name a canon file, so an
agent can't cite a playground or supporting file as canon. The Console
shows each cite's scope. (Design 1.2, 5.2 and 7.4, the playground
ruling, 51fdfb7e2, and the ruling after r11. Q176 Case 2, the
playground items, TARGET.md:92-94. Version 1.14: the levels and the
draft. Version 1.15: supporting.)
placement rules. A Markdown file's scope comes from its path and from
past imports, prefixes first: a Markdown file under the archive folder
is archived, one under a supporting prefix is supporting, a Markdown
file at a converted file's old path is unmapped under REQ-LIB-8 but
flagged in the format map, one under a playground prefix at any level is
playground, any other Markdown file in a book folder is canon, and
everything else is unmapped and never returned. A folder in both lists
is supporting. The archive folder follows the same placement rules and
may not overlap another prefix or a book folder. An archived file keeps
the path it had under the archive folder, and its universe and book are
those of that old path. A DOCX document in the writing store keeps its
source's scope: the universe, the book if there is one, and canon or
playground. Its source is the Markdown file it was converted from or,
for one the human starts in the editor, the universe and book or the
playground the human picks (REQ-LIB-8). A playground DOCX follows the
playground rules and is never returned as canon. It stays playground
after its draft universe is made real. In version 1.17 no promotion or
mapping change makes a playground DOCX canon. A later version of this
PRD says how one becomes canon. Lookups return canon unless the caller
asks for playground, supporting or archived. Every answer marks an
archived file as archived and names the DOCX that replaced it. A
citation marked as canon must name a canon file, so an agent can't cite
a playground, supporting or archived file as canon. The Console shows
each cite's scope and format, and whether a cited Markdown file was
converted. (Design 1.2, 5.2 and 7.4, the playground ruling, 51fdfb7e2,
and the ruling after r11. Q176 Case 2, the playground items,
TARGET.md:92-94. Version 1.14: the levels and the draft. Version 1.15:
supporting. Version 1.17: archived scope and DOCX scope.)
- **REQ-LIB-3.** Universes stay separate. Every lookup names one universe,
and its results never leave it. A draft universe (REQ-LIB-2) counts as
a universe here. It has no canon, so a lookup in a draft asks for
@@ -1231,23 +1296,62 @@ library is the user's own writing repository.
A pin doesn't stop the change. The continuity agent reads the human's other
constraints and cites them. (Design 1.4 and 3. Q176 Case 2, the
crossover items.)
- **REQ-LIB-4.** Git holds every change. The user commits and pushes with
their own tools, today Obsidian with obsidian-git. The bus keeps a
- **REQ-LIB-4.** Git holds every change. The user commits and pushes
Markdown with their own tools, today Obsidian with obsidian-git. A
converted story file's current text is no longer in that repository,
which keeps only its archived Markdown (REQ-LIB-8). The bus keeps a
mirror of the remote, fetched with a read-only token every 60 seconds
and before any lookup when the last fetch is older than that. It only
fast-forwards. A force-push is held as `library-diverged` until a human
accepts it. Agents read only the mirror's served commit and never hold
a copy of their own, so they see the user's push within about a minute.
Bus writes and pushes come in later rows (C2-02, C2-03b). (Design D5,
D6, 7.1, 7.2 and 7.5. Q176 Case 2 "Git is used for all changes -- local
and remote" and the sync items.)
accepts it. Agents read the user's repository only at the mirror's
served commit, except a story file during its import under REQ-LIB-8.
They read a DOCX story file only at a committed version in the writing
store, under REQ-LIB-5. They never hold a copy of their own, so they see
the user's Markdown push within about a minute and a DOCX save at their
next lookup after the editor reports it as committed. Bus writes and
pushes come in later rows (C2-02, C2-03b). A DOCX story file is saved in
the Console's editor, and each accepted save is one commit by the human
in the stack's writing store (REQ-LIB-9). Each writing-store commit is
also pushed to a remote off the stack's host, never by force-push. The
remote is a setting only a human changes, and it starts unset. A
repository on the stack's host never counts as that remote. A missing
remote or a failed push never undoes a save, and the user sees each push
that is pending or failed. Only the stack's host pushes to that remote,
and no agent can reach it with any credential. Agents read the local
commit. (Design D5, D6, 7.1, 7.2 and 7.5. Q176 Case 2 "Git is used for
all changes -- local and remote" and the sync items. Version 1.17, with
Q222 for the remote setting.)
- **REQ-LIB-5.** Agents check the library through typed lookup tools:
find, get, search, manuscript and universe. Each answer names the
commit it read. Get returns a file's links out and links in, so the
connections are visible to agents. The user sees them in Obsidian's
graph and backlinks. (Design 3 and 7.1. Q176 Case 2 "The agents have
tools to check character, place, and other data", the visualization
item and "Connections are always visible".)
find, get, search, manuscript and universe. Each answer names the commit
it read. Each answer also names the file's format and where it lives:
its path, or its document id in the writing store. A DOCX story file is
read from its latest committed save, never from unsaved text. A lookup
that starts after the editor reports a save as committed returns that
save or a later one, even while its push to the remote is pending or has
failed, and the answer names the version. Get, search and manuscript
read a DOCX story file as its extracted text with DOCX locators, never
as lines. That text is the file with its tracked changes accepted. An
agent reads a story file's comments, or its text with tracked deletions
shown, only after a human turns on the matching setting. Both settings
are off by default, and only a human changes them. The reader in this
version can't read some things a DOCX story file can hold, such as a web
link, a text box, a field or hidden text. The editor warns the human
when an edit adds one, and the human can still save it. Until a later
save takes it out, lookups give agents none of the text of the part that
holds it, whether that is the chapter's body, a note or a comment. The
manuscript lookup follows the book's chapter order across both formats
(O2 design D10), and the next chapter it names is never an archived
file. Links out and in resolve across both formats. The universe lookup
returns the format map (REQ-LIB-8). Get returns a file's links out and
links in, so the connections are visible to agents. The user sees a
Markdown file's links in Obsidian's graph and backlinks. A DOCX story
file's wikilinks stay clickable in the Console's editor (O2 design D11),
and no graph shows them yet. The Console's Connections view lists the
current links and backlinks of each story file the user may see, in both
formats, with each one's scope and format. (Design 3 and 7.1. Q176 Case
2 "The agents have tools to check character, place, and other data", the
visualization item and "Connections are always visible". Version 1.17,
with Q222 for the two settings and Q239 for the Connections view.)
- **REQ-LIB-6.** The creative roles are ideas, research, proofreader,
fact-checker and continuity, plus a companion role that the user's own
named agents run as. Agents never write the story. A creative agent may
@@ -1255,22 +1359,37 @@ library is the user's own writing repository.
folders a human marks (REQ-LIB-1), such as character sheets, summaries,
cross-link notes and research notes. Every other path in the user's
repository is read-only to it, chapters and unmarked folders included,
and the broker refuses such a write before any git call. Each agent
write is one commit by that agent's own Gitea account, with a trailer
naming the agent. The user sees a notice for it and can revert it. Its
tools are the lookups, a message to the human and the two write tools,
and research and companion also fetch web pages through the broker.
Agents talk with the user in free text. A reply may cite lines of the
repository at a commit or pages the agent fetched, and the broker
checks every citation before it stores the message. A setting,
`requireCitation`, makes every reply carry a citation, except the
runner's fixed notices for no answer, a failed turn, no answer passing
the citation check and an answer too long to send. Only the human
changes it, and it is off by default. The user sees only stored
and the broker refuses such a write before any git call. The writing
store and the archive folder are read-only to a creative agent as well,
and the broker's path check treats the archive folder as story. No agent
creates, edits, deletes, converts, archives or restores a story file in
any format, and no agent writes a DOCX. No creative agent starts or
undoes a conversion, and none asks for one through a tool or a decision
it raises, as REQ-LIB-8 says. An agent may still suggest a conversion to
the human in chat. Each creative role's contract states which files are
DOCX and which are Markdown, and cites a DOCX story file by its locator,
never by line. Each agent write is one commit by that agent's own Gitea
account, with a trailer naming the agent. The user sees a notice for it
and can revert it. Its tools are the lookups, a message to the human and
the two write tools, and research and companion also fetch web pages
through the broker. The write tools need the action `library.write`,
which routes under this requirement within the bounds REQ-WAIT-3 names
(version 1.16). Agents talk with the user in free text. A reply may cite
lines of a Markdown file at a commit, a place in a committed DOCX story
file, or pages the agent fetched, and the broker checks every citation
before it stores the message. A DOCX citation never calls a paragraph a
line. It names the document id, the committed version and a DOCX
locator. A citation that names an archived file as current is refused.
The `requireCitation` setting below applies to Markdown and DOCX
citations alike, and so does the rule for a citation marked as canon. A
setting, `requireCitation`, makes every reply carry a citation, except
the runner's fixed notices for no answer, a failed turn, no answer
passing the citation check and an answer too long to send. Only the
human changes it, and it is off by default. The user sees only stored
messages, on every channel. (Design 4, 5.2 and the ruling after r11.
Q223 and Q226. Q180 for Bean and Chet. Q176 Case 2, the agent list,
"User sets strict guardrails for agent capabilities" and TARGET.md:101
"Agents NEVER write prose for the user". Version 1.15.)
"Agents NEVER write prose for the user". Version 1.15. Version 1.17.)
- **REQ-LIB-7.** The user talks to several creative agents in one chat. A
room holds one thread per creative agent in a project. A message goes
to every agent in the room, or only to the agents named by @tags at its
@@ -1278,6 +1397,110 @@ library is the user's own writing repository.
the threads as one timeline, and each agent sees only its own thread.
(Design D4 and 6, row C2-09b. Q176 Case 2 "converse with N agents in
one chat session with @tagged messages".)
- **REQ-LIB-8.** Story files become DOCX. DOCX is the standard for chapters and
the other story text in a Case 2 writing project, canon and playground alike. A
story file becomes DOCX through a human's import (below), or when the human
starts it in the editor. From then on DOCX is its only working format, edited in
the Console's editor (REQ-LIB-9). A Markdown story file stays Markdown until its
import. Files in a type folder (REQ-LIB-1), such as character and place notes,
stay Markdown. So do supporting files, agent files, instructions, PRDs and
documentation, including the character sheets, summaries and cross-links agents
keep under REQ-LIB-6.
- A converted story file's current text leaves the user's Gitea-mirrored
repository. Its DOCX lives in the stack's writing store, on the stack's host,
and not in the repository the user pushes or opens in Obsidian. Only its
archived Markdown stays there.
- An import converts one Markdown story file, canon or playground, outside every
type folder (REQ-LIB-1), to a DOCX document in the writing store. It refuses
an unmapped, supporting, archived or type-folder file, and a file that is
already converted. Each import has its own fidelity report, the human's
confirmation of that report, a manifest naming the source file and its commit,
and one commit in the store. The DOCX keeps every wiki link of the source as
written, and a lost link to another library file fails the fidelity report. A
web link becomes plain text that keeps its label and address, and the fidelity
report lists it for the human to confirm. A web link that can't be kept safely
as plain text refuses the import.
- The human can also start a new story file in the editor, in a universe and
book or a playground the human picks. It is a DOCX document in the writing
store from its first commit, with no Markdown copy, and it opens under the
same gate as the import.
- The same import marks the Markdown file converted, naming the document, and
moves it to the archive folder under its old path (REQ-LIB-1, REQ-LIB-2).
The mark and the move are one commit authored by the human in the user's
repository. A new human-only bus step (Vikunja 571) on the C2-02 write
plumbing writes it, and C2-03b pushes it. The move keeps the bytes and the
git history, so it isn't `data.delete`.
- The store commit comes first, and the writing store's remote must confirm it
holds that commit. Then come the archive commit and its push, then publication
to the lookups. Until publication the Markdown at its old path keeps its canon
or playground scope. Agents read that file at the commit the import started
from, even after the mirror's served commit reaches the archive commit or a
later one. No lookup returns the file's archived copy before publication, even
one that asks for archived files. A refusal or failure before the archive push
leaves the Markdown unmoved and no DOCX that a lookup returns. A failure after
the archive push leaves a pending import that the human sees and can resume,
never a claim that nothing moved. An archive push whose result is unknown
counts as a failure after the archive push until the stack reads the result
back from the user's remote.
- Each stage of an import writes a logged event. The import's record on the
stack's host names the source file's path, commit and blob and the new
document id, and each event points to that record.
- After a conversion, a Markdown file at the converted file's old path is
neither canon nor playground. Lookups leave it out, the format map flags it,
and the human is told.
- A new Markdown story file in a book folder that holds converted files stays
canon until its import, unless it sits at a converted file's old path or under
a playground prefix. The Console flags it and offers the import. Nothing
converts it on its own.
- A hybrid's exclusion that covers a converted file's old path also covers its
DOCX document (REQ-LIB-3). An exclusion of a whole book folder also covers
every DOCX document in that book, including one started in the editor. An
exclusion can also name a DOCX document by its id.
- Only a human starts an import or a new story file, from the CLI or a Console
session signed in as that human. Both are human verbs outside the agent action
vocabulary. Neither is added to `ACTIONS` in the business vocabulary or to the
broker's always-gated list, including the import's action, `library.convert`.
No role holds them, no agent can request them, and the hard-wait list in
REQ-WAIT-1 doesn't change. In an organization with several humans, who may
start them is a setting only a human changes, and the default is the owner
only.
- Conversion is one way, with no parallel run. Neither the Console nor any agent
edits a converted story file as Markdown, and no DOCX edit syncs back to
Markdown. The Console's Markdown save refuses every file under the archive
folder. The user's own tools can still change the archived Markdown, and no
such change reaches the DOCX. The old Markdown stays in git history, and
nothing rewrites history. A promotion into a book folder (C2-19) runs the
import for a Markdown story file or is refused. A promotion of a playground
DOCX is refused in version 1.17.
- Agents get a format map: for each mapped folder and story file, its format,
scope and where it lives, archived files, flagged files and pending imports
included.
- The import and new story files ship closed. They open only after four things.
The editor ships. The human has set the writing store's remote, and the stack
has checked that it can push there. Agents can read committed DOCX story files
through the lookups and cite them (REQ-LIB-5, REQ-LIB-6). Any earlier agent
system that writes the user's files has stopped, which for Case 2 is the
Hermes retirement (#423 step 5). So the import itself never cuts agents off
from a chapter, the chapter's current text never lives only on the stack's
host, and no second system writes the story. The first import of a real user's
books waits for Jason's go.
(Q235, Q236, Q237: P1 to P6. Q222 for the setting. O2 design D10 to D12.
Version 1.17.)
- **REQ-LIB-9.** The Console's DOCX editor. The human edits a DOCX story file in
the Console's editor, in a split pane with the agent chat alongside it. The
human can pin an agent chat to a document. The human can select a word,
right-click it, and choose a synonym from a thesaurus. Only a human saves. Saves
happen on their own while the human types (Q226), at intervals only a human
changes, and each accepted save is one commit with the human as author, pushed
as REQ-LIB-4 says. A save made against an older version than the current one is
refused. Agents never write a DOCX. Versioning is the editor's tracked changes
plus one git commit per save. The editor's code is vendored and pinned, with a
hash per file and the licenses that its independent security review accepted. At
runtime the editor loads only files that the stack itself serves, and it
requests nothing from any other host. The editor's frame sends exactly the
content security policy Jason approved in Q234, and the WebUI shell's own header
is unchanged. REQ-PROJ-3's project docs editor is unchanged. (Q195, Q226, Q234,
Q236. O2 design D3, D4, D6, D8, D10, D13, D14, D16 and D22. Version 1.17.)
### Installer
@@ -1750,6 +1973,118 @@ latest-by date and a hard target (Q176).
ruling after r11 and in the Q226 briefs of
`docs/plans/2026-10-20_case2-rows.md`. Sage files the rows under
Vikunja 416. This amendment adds the requirements only.
- Version 1.16:
- REQ-WAIT-3 gains one paragraph, the `library.write` routing and its
bounds. REQ-LIB-6 gains one sentence naming the action. No other
requirement text changes.
- `packages/tasks/requirements.json` changes its version to 1.16 and
nothing else. The titles stay and the id count stays 85.
- The commit that adds `library.write` to `ROUTED` in the business
vocabulary and the broker is C2-08b, row 285. It lands only after this
version.
- Version 1.17:
- Jason confirmed the reading 'for now (subject to future change)' (Q237). The
rulings stand until he revises them.
- The 10-27 Case 2 release ships on Markdown as built (P6). The 1.11 line 'The
Case 2 MVP for 10-27 is the Markdown library (#416). The WYSIWYG editor (Q195,
Q196) is an add-on that doesn't gate it.' still holds. Conversion opens after
10-27, and only once REQ-LIB-8's four conditions are met. The editor ships,
the writing store's remote is set and the stack has checked that it can push
there, agents can read and cite committed DOCX story files through the
lookups, and Hermes is retired, which is #423 step 5.
- The 1.12 note says five bus write and Console editor rows, C2-02, C2-03b,
C2-11a, C2-11b and C2-19, don't gate 10-27. Jason's Q226 F42 later moved
C2-02, C2-03b and C2-11b into the 10-27 slice. The Case 2 design brings C2-11a
in with them, because C2-11b needs it. C2-19 stays outside the slice. Mos's
reading of F42 is that C2-02 and C2-03b now gate 10-27, because design 9.2
puts them in 'the smallest slice that lets her start'. C2-11a and C2-11b are
slice rows that still don't gate 10-27, because the editor is the Q195 add-on.
The DOCX change in 1.17 leaves this slice as it is, and C2-11b's Markdown
editor never edits a converted story file.
- Q236 narrows Q176's editor choice to DOCX for story files (REQ-LIB-9). The
1.12 note that the Case 2 design keeps Obsidian now holds only for Markdown
files, including story files not yet imported. A DOCX story file is edited
only in the Console's editor under REQ-LIB-9.
- This replaces two parts of the O2 design at 25c09f19f. The superseded-notice
proposal from Fenton's F1 in D12 is withdrawn, and the
keep-Markdown-at-its-path model of D11 is replaced. Gyro's rewrite of the O2
design for Vikunja 571, landed on `refactor` at b99ca86b9, carries the
details, and where it differs from this text, this text holds:
- the import and archive steps, the mark, and the new bus step;
- links from supporting files to a moved chapter;
- the DOCX reader;
- the user's unpushed or later changes to an archived file or at its old path,
and pushing and closing the file in Obsidian before each import;
- the writing store's remote in W4 section 7.3, which replaces the no-remote
and no-backup rule that W4 section 7.4 held before the rewrite, at
062646487;
- import events counted by a bus restore, and recovery when a bus restore or a
restore of the whole host predates an import;
- manifest order for the manuscript lookup and its next chapter;
- `library.convert` kept out of `ACTIONS`, out of the hard-wait note and out
of the pinned `GATED_ONLY` list in packages/business/src/vocabulary.mjs.
- Agents read a converted story file through the Case 2 lookups under the same
project and universe access as Markdown canon, in any thread or room. W4's
earlier O2-only conversations, per-document grants and per-send sharing don't
gate these lookups. The 571 rewrite replaced them in W4 sections 7.1 and 7.2.
The creative tool list in REQ-LIB-6 doesn't change.
- The six creative role contracts row 213 landed say 'Every item cites the
lines of the user's material it is about'. A build row under 571 changes that
line for DOCX story files (REQ-LIB-6).
- A new Markdown file in a book folder is still canon in 1.17. The 1.17
exception is a file at a converted file's old path, which REQ-LIB-2 makes
unmapped.
- Story files convert whether canon or playground. Type-folder and supporting
files stay Markdown.
- Measured 2026-10-23 at about 01:21Z: the Case 2 user's library repository
tracks 213 files, 203 of them Markdown, and none is a .docx or .doc. It has no
untracked files. The user's .doc files (Q196) are outside it. So the landed
code has no DOCX there to report as unreadable at 10-27.
- Text replaced in 1.17, kept here as in earlier notes:
- Creative library lead, the 1.16 sentence: "The library is the user's own
writing repository."
- REQ-LIB-2, the 1.16 sentences: "A file's scope comes from its path alone,
prefixes first: a Markdown file under a supporting prefix is supporting,
one under a playground prefix at any level is playground, any other
Markdown file in a book folder is canon, and everything else is unmapped
and never returned. A folder in both lists is supporting." "Lookups return
canon unless the caller asks for playground or supporting." "so an agent
can't cite a playground or supporting file as canon" "The Console shows
each cite's scope."
- REQ-LIB-4, the 1.16 sentences: "The user commits and pushes with their own
tools, today Obsidian with obsidian-git." "Agents read only the mirror's
served commit and never hold a copy of their own, so they see the user's
push within about a minute."
- REQ-LIB-5, the 1.16 sentence: "The user sees them in Obsidian's graph and
backlinks." A converted chapter's connections show in Obsidian only as they
stood at conversion, and the Console's Connections view shows their
current links.
- REQ-LIB-6, the 1.16 sentence: "A reply may cite lines of the repository at
a commit or pages the agent fetched, and the broker checks every citation
before it stores the message."
- REQ-LIB-1 only gains sentences.
- REQ-LIB-3's text and REQ-LIB-7 are unchanged. REQ-LIB-8 adds one form of
exclusion REQ-LIB-3 doesn't have: naming a DOCX document by its id. A path
exclusion also covers DOCX documents, through a converted file's old path or a
whole book folder. An archived file and a DOCX document keep a universe, and a
book where their source has one, so every lookup still names one universe.
REQ-PROJ-3, the Console editor for a project's spec and docs, is unchanged.
C2-11b opens the library's Markdown files in that same editor.
- In `packages/tasks/requirements.json` the titles of REQ-LIB-1, REQ-LIB-2 and
REQ-LIB-4 change, and those of REQ-LIB-5 and REQ-LIB-6 stay. REQ-LIB-1 becomes
'A human-set library maps the user's repository onto universes, books, types,
playgrounds, supporting and archive folders'. REQ-LIB-2 becomes 'Scope comes
from the path or a DOCX's source, lookups default to canon, and only canon
files are ever cited as canon'. REQ-LIB-4 becomes 'Git holds every change. The
bus fast-forwards a read-only mirror every 60 seconds, the one place agents
read Markdown'. REQ-LIB-8 and REQ-LIB-9 add two ids, so the count goes from 85
to 87. REQ-LIB-8 is titled 'Case 2 story files become DOCX by a human-only
one-way import or in the editor; Markdown stays for notes and docs', and
REQ-LIB-9 'The Console's DOCX editor: only a human saves, each save is one
commit, and the editor code is vendored and pinned'.
- Open points O1 to O4 of draft 1 were ruled by Mos in mos-1367 B1 to B4.
- Sage files the build rows under Vikunja 570 and 571 after the rewrite is
approved. This amendment adds the requirements only.
## Risks and open questions
@@ -1788,6 +2123,26 @@ latest-by date and a hard target (Q176).
seat sandbox with its own network namespace closes it. Until then the
host's own audit log is the check after the fact (row 210 design section
6).
- A DOCX file is a zip archive, so git can't show a line diff of a chapter.
Review of a story change goes through the editor and the committed reader,
not `git diff`. (Version 1.17.)
- A converted chapter's DOCX is not in Obsidian's graph. Obsidian shows the
chapter only through its archived Markdown, and no DOCX edit reaches that file's
links. The DOCX's current links show in the Console's editor and its
Connections view, and no graph shows them yet. (Version 1.17.)
- Conversion can't be undone in place. Front matter, wiki link forms and
unsupported blocks may not survive it, and the mirror can't refuse the user's
push to an archived file. The controls are the fidelity report, the human's
confirmation, an untouched original on refusal, and the kept archive.
(Version 1.17.)
- A restore of the stack's bus alone from a backup taken before an import drops
the import's logged events, while the archive commit and the DOCX stay. A
restore of the stack's whole host from such a backup also takes the writing
store and the host's import records back to before the import. The archive
commit stays in the user's Gitea repository and the DOCX stays on the writing
store's remote, but the host keeps no record of the import. A bus restore counts
import events, and the import's recovery covers both cases (Vikunja 571).
(Version 1.17.)
## Milestones
Loaded 100 of 178 files, more files were not shown because too many files have changed in this diff. Show more