Compare commits

..
Author SHA1 Message Date
jason.woltje 7c53adf106 Untested: record row 283 follow-up dependency receipts (#1793) 2026-10-23 15:02:54 -05:00
jason.woltje 4fd790faa5 Untested: merge row 283 verified helper follow-up for row 288 2026-10-23 15:02:41 -05:00
jason.woltje b2a1f40926 row 283 (#1788): BUILD.md: T2 receipts on c18ad3e12 (sage-3695): base 16+6+6+7 library and 21 business green, 14/14 mutants killed on their named tests, library 98/98 and business 231/231; the mos-1272 first-run red and its test-helper fix; doc only 2026-10-23 15:02:14 -05:00
jason.woltje c18ad3e12e row 283 (#1788): mos-1272 first-run red: supporting.test's refuses() labelled each assertion with JSON.stringify(path), which throws on the 1n case added for E3; it now uses util.inspect. Test helper only 2026-10-23 15:01:35 -05:00
jason.woltje 31fd2faebd Untested: clarify existing blob requirement in library delete tool (#1793) 2026-10-23 14:50:51 -05:00
jason.woltje 98ca25ab1a Untested: record row 288 T7 verification and landing boundaries (#1793) 2026-10-23 14:47:05 -05:00
jason.woltje f8f79ea2f7 Untested: merge row 284 reviewer test follow-up for row 288 2026-10-23 14:45:53 -05:00
jason.woltje cd2f50daec Untested: add library write tools and served blob integration (#1793) 2026-10-23 14:45:31 -05:00
jason.woltje 5d587a552d row284: cover absent supporting prefix directly (unrun) 2026-10-23 14:44:59 -05:00
jason.woltje 29455c0c5d Untested: migrate creative tool snapshots to eight or ten names (mos-1523) 2026-10-23 14:44:53 -05:00
jason.woltje c0ca3894c0 Untested: merge complete row 284 prerequisite for row 288 2026-10-23 14:41:30 -05:00
jason.woltje 47db04eba8 row284: supporting lookup scope and served blob IDs (unrun) 2026-10-23 14:39:33 -05:00
jason.woltje 77eb46ff5e Untested: record notice-store failure publication behavior (#1793) 2026-10-23 14:36:54 -05:00
jason.woltje cef503cb93 Untested: retain library notice after push when publication fails (#1793) 2026-10-23 14:35:47 -05:00
jason.woltje 0ed4fbab9b row284: migrate playground get tests under mos-1330 (unrun) 2026-10-23 14:33:28 -05:00
jason.woltje fc05bcbc39 row284: merge row224 intact onto row283 prerequisite (unrun) 2026-10-23 14:32:34 -05:00
jason.woltje 7c01a5ad7a Untested: use editor login and cover row 288 early review gaps (#1793) 2026-10-23 14:32:10 -05:00
jason.woltje 48781a8421 Untested: merge row 283 editor login dependency for row 288 2026-10-23 14:29:34 -05:00
jason.woltje 46bad7ea3b row 283 (#1788): sage-2925/sage-3649: optional editor.user (her Gitea login for library-human, read by row 288), checked with USER as the entry user is, kept in the output only when given; T3 accepted and refused cases; README; mutants M6e/M6f; BUILD.md records the departure from DESIGN.md section 1. Untested 2026-10-23 14:28:44 -05:00
jason.woltje 7dc4fb2cca Untested: protect library publication state and add broker bare-repo coverage (#1793) 2026-10-23 14:24:34 -05:00
jason.woltje c867cada97 Untested: row 288 library writes, notices, revert and human saves (#1793) 2026-10-23 14:20:42 -05:00
jason.woltje 436b27f662 Untested: merge row 283 test environment dependency for row 288 2026-10-23 14:20:08 -05:00
jason.woltje e67c27b322 row 283 (#1788): webby-0236 E5: the git round-trip test drops GIT_DIR, GIT_WORK_TREE, GIT_COMMON_DIR and GIT_INDEX_FILE and reads no global or system config (repo convention); BUILD.md records E5. Untested 2026-10-23 14:15:06 -05:00
jason.woltje 539079b045 Untested: merge row 283 E4 dependency for row 288 2026-10-23 14:14:03 -05:00
jason.woltje 76d651dba4 row 283 (#1788): E4 per sage-3606: editor values starting or ending with git's crud() set (bytes <= 0x20 and , : ; < > " \ ') refuse library-mapping-invalid; T3 cases per character and key, inner chars pinned, git round-trip test; mutants M6c/M6d; BUILD.md cites crud() and records the claim (rev 3642). Untested 2026-10-23 14:12:24 -05:00
jason.woltje e66ec9f42d Untested: merge row 286 dependency for row 288 2026-10-23 14:10:45 -05:00
jason.woltje a45459a1c9 Untested: merge row 285 dependency for row 288 2026-10-23 14:10:39 -05:00
jason.woltje c5759f5ac1 Untested: merge row 283 dependency for row 288 2026-10-23 14:10:35 -05:00
jason.woltje 88ec5e8e47 row 283 (#1788): E2 per sage-3601: editor values with surrounding whitespace refuse library-mapping-invalid (git trims idents at commit); T3 padded cases per key, mutant M6c; BUILD.md records the departure from DESIGN.md section 1 and the fixture grep (none). Untested 2026-10-23 14:02:37 -05:00
jason.woltje b9ddb6c5e2 row 283 (#1788): webby-0227 E3: supportingPath checks the type before stringifying, so a non-string path refuses library-write-story-path; string case covers 7, null, undefined, 1n and an array; BUILD.md records E1-E3, E2 to Sage. Untested 2026-10-23 14:01:33 -05:00
jason.woltje bd2c1c4ea1 row 283 (#1788): BUILD.md: drift check, N3 grep, mos-1330 :73 old and new text, Q2 tradeoff, Q4 handoff to row 288 via #1793 c30738, mutants M1-M8 exact strings, gate plan; doc only 2026-10-23 13:59:07 -05:00
jason.woltje 28918824ca row 283 (#1788): survey.test:73 re-pin under mos-1330 item 4. The scopes line gains only supporting 0; old 'scopes canon 5, playground 1, unmapped 3; indexed 6', new 'scopes canon 5, playground 1, supporting 0, unmapped 3; indexed 6' 2026-10-23 13:58:17 -05:00
jason.woltje 331653b385 row 283 (#1788): C2-01d supporting folders. supporting lists on every universe kind with the playground's prefix rules, library-supporting-other-universe and the cross-list rules; optional editor {name, email} refusing controls, < and >; classify checks supporting before playground; supportingPath with the eight checks over the listTree array, exported; survey counts supporting (sage-2923); README; tests T1-T8. Untested: runs wait for Sage's CLEAR 2026-10-23 13:58:13 -05:00
jason.woltje 53db6b7cac row 285: BUILD.md records N1, the creative-only library.write refusal as a follow-up, and the round scope (#1790, sage-3593) 2026-10-23 13:57:46 -05:00
jason.woltje 8f8b8cf153 row 285: library.write joins ACTIONS and ROUTED in the business vocabulary and the broker under PRD 1.16 REQ-WAIT-3 and REQ-LIB-6, with a hard-waits test; BUILD.md records the r2 rebase, range-diff and drift check (#1790, sage-3578) 2026-10-23 13:56:55 -05:00
jason.woltje 573fa0be06 Row 286 C2-03b r2: rebase onto 45dca69f6 (sage-3579). The range-diff is all =. Drift check of the paths the tests read is in BUILD.md. Unrun. 2026-10-23 13:54:33 -05:00
jason.woltje cf8ba6ef45 row 285: mos-1333, withdraw the publish fence; N10 option (a) with an assistant instance; a chief with its gitea ref, identity and publish loads; restore the creative publish test; BUILD.md gate scope (sage-2946) (#1790) 2026-10-23 13:54:09 -05:00
jason.woltje 66e3c3d616 row 285: webby-0178 early read, drop the stale creative publish comment and name the example's test readers (#1790) 2026-10-23 13:54:09 -05:00
jason.woltje aabfdfc604 row 285: a chief-of-staff or creative instance that names publish refuses at load, keeping today's N10 refusal; instance-settings :289 follows the companion's gitea (#1790, mos-1332)
The fence sits near business.mjs:166, clear of row 287's :283-291. Its message
starts with the N10 text. An assistant-instance test keeps the no-gitea branch
covered, and a creative-instance test covers the fence. Nothing here has run.
2026-10-23 13:54:09 -05:00
jason.woltje 115889e4a0 row 285: fixtures, example and README give the chief and creative instances their gitea reference (#1790, mos-1330)
Fixture data only, plus two named tests that a chief or creative instance
without credentials.gitea refuses at load. chief-of-staff.test :88 and :155
change expected values and wait on Mos's mos-1272 ruling. Nothing here has run.
2026-10-23 13:54:09 -05:00
jason.woltje 9a77fff7a7 row 285: C2-08b, creative roles hold library.write and the gitea credential, the chief of staff a read-scoped gitea credential; #giteaRole refuses the chief (#1790, mos-1309 item 5, sage-2915, sage-2921)
The creative role files declare gitea with SEAT_SCOPES, the scopes the Gitea
grant mints. The chief declares read scopes only, so #giteaRole refuses it
with gitea-not-declared until row 287 mints its declared scopes.
library.write is not yet routed: it waits on PRD 1.16 (row #1796), and the
fixture change waits on Mos's mos-1272 rulings. Nothing here has run.
2026-10-23 13:54:09 -05:00
jason.woltje 4d6c8234fa Row 286 C2-03b: BUILD.md notes for dewey-0869 and sage-2951 (fired-chief path :4308, marker-gated busy test, T9 scratch roles) 2026-10-23 13:53:09 -05:00
jason.woltje a1876928f4 Row 286 C2-03b: fold dewey-0869 and sage-2951 (marker-gated busy test, T9 scratch roles with a chief gitea credential, fired-chief path cited) 2026-10-23 13:53:09 -05:00
jason.woltje ae48662afa Row 286 C2-03b: cite sage-2947 (T7 real verbs, R1 in this row, 285/286 rebase rule) 2026-10-23 13:53:09 -05:00
jason.woltje 44edead6d8 Row 286 C2-03b: fold dewey-0864 (T7/T9 through grants.revoke and instance.fire, race fetch takes the sync guard, two-chief code, id -u shim note) 2026-10-23 13:53:09 -05:00
jason.woltje b0d21c41a9 Row 286: library.status shows the credential (webby-0151 R3); fired chief revokes its row as row 281 does; unrun 2026-10-23 13:53:09 -05:00
jason.woltje 61ef385e4a Row 286 C2-03b: library push, per-seat credentials, the chief's fetch credential, askpass fd check (#1791, sage-2922/2925/2928, dewey-0860); unrun 2026-10-23 13:53:09 -05:00
jason.woltje d46b6d6ef6 Row 286 C2-03b: sage-2928 rulings (sh helper, chief by roles definition, departure noted) 2026-10-23 13:53:09 -05:00
jason.woltje edec640d29 Row 286 C2-03b: fold dewey-0860 (Q1 sh fd/inode check, Q2 chief by roles definition, N1-N3) 2026-10-23 13:53:09 -05:00
jason.woltje dd3e3c0963 Row 286 C2-03b: sage-2925 Q4 ruling (humanCredential takes her login; editor.user is row 283's) 2026-10-23 13:53:09 -05:00
jason.woltje b3cf04b371 Row 286 C2-03b: sage-2922 files-owned extension; Q4 back to Sage (user is the bot fetch identity) 2026-10-23 13:53:08 -05:00
jason.woltje 4faa3f6e9a Row 286 C2-03b: build plan and four scope questions (BUILD.md only) 2026-10-23 13:53:08 -05:00
jason.woltje 4f66dccdec row 283 (#1788): design draft 2 folds webby-0169 R1 (editor refuses < and >, T3, M6b) and N1 (check 8 compares NFC and case, NFD case in T6, M4b); Q2-Q4 accepted, Q4 handoff to C2-02 in BUILD.md; doc only 2026-10-23 13:52:54 -05:00
jason.woltje 398314cf4d row 283 (#1788): design draft 1. supporting lists with the playground's prefix rules and library-supporting-other-universe, cross-list rules, the optional editor, supporting first in classify, supportingPath over the listTree array; survey.mjs departure Q1 for Sage, Q2-Q5 for Webby; doc only (sage-2906) 2026-10-23 13:52:54 -05:00
jason.woltje 23816cf285 row224: pace failed inline sync attempts (unrun) 2026-10-22 11:50:19 -05:00
jason.woltje 38e322ea01 row224: draft broker library lookups and typed tools (unrun) 2026-10-22 11:45:58 -05:00
jason.woltje 4f3fbc25a0 row 216 (C2-05a, #1717): library index cache and lookup queries find, get, search, manuscript, universeInfo; get pages links, manuscript takes no chapter, search marks a too-long line, PRD 1.14 levels (rulings da9555ca2); unrun 2026-10-22 09:42:51 -05:00
247 changed files with 4050 additions and 24203 deletions

No files matched your search

+2 -54
View File
@@ -5412,58 +5412,6 @@ Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
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 | Gladstone | Gitea #1816 / Vikunja 614 hostcheck item 5 and bundle verification | Before: dpkg's correct 750 form falsely fails item 5, and two run-sheet bundle verifies require a missing repository. Draft after: exact root/mosaic/path with octal 0750 equivalence; stat/output unchanged; both verifies use cleaned-up bare scratch repos. Three additive-only test blocks, two planned mutants. Untested under Sage-3738; Sage-3740 authorizes untested branch push for static review. Queue row pending; Sage owns gate and landing.
## 2026-10-23 — Row 288 C2-02 build starts (Bubba, #1793)
2026-10-23 | Gladstone | #1816 / Vikunja 614 scope amendment mos-1534, sage-3741 | F1 narrowed to exact 750/0750 and single spaces; F3 allows @ in absolute unit paths with unsafe-character refusals retained; F4 moves copy_tree to / after path resolution. AS_USER audit found git -C and stack-release's existing checkout cd cover the other steps. Added new unit and unreadable-cwd copy tests only. Untested under Sage's no-Node hold; no live action.
2026-10-23 | Gladstone | #1816 / Vikunja 614 F5 draft (mos-1536) | Before: the run sheet omits the explicit notify decision and precheck misses a bus-start refusal. After: section 9a creates a private null-binding decision as mosaic; precheck directly reuses readNotifyConfig and private_dir, and new tests cover accepted and refused decisions. Existing fixture setup additions await Mos approval through Sage; installer unchanged. Untested under no-Node hold.
2026-10-23 | Gladstone | #1816 / Vikunja 614 fixture ruling mos-1537 | Added only three setup lines each to host(t)/world(t) for the explicit null notifier decision. Existing assertions/fakes/lines intact. New install test checks decision bytes/mode preserved and missing decision remains absent. F5 missing-file fault removes the setup file. Untested under Sage's hold; separate follow-up atop 33950cf9b.
2026-10-23 | Gladstone | #1816 / Vikunja 614 F6 and notify parent (mos-1537) | Before: the run sheet's copy check lacks its required CLI wrapper; mkdir -p -m does not guarantee the notify parent mode. After: a separately Mos-gated wrapper --create step follows install and precedes check, as mosaic from cwd /; section 9a explicitly creates both notify directories at 0700. Docs only, no live action; Node/tests still held.
2026-10-23 | Gladstone | Row320 / #1816 first review CHANGES (morgana-0217) | Reviewer run: hostcheck19/19, unit47/47, install24/24, copy17/18; seven of eight mutants killed, M4 survived because both base/mutant failed only cleanup. Fixed this row's F4 test to enter cwd then chmod000, assert no read/search, pass an absolute outside bundle, and restore0700 in finally. Existing tests/setup and production scripts unchanged. Correction untested locally pending Morgana's focused rerun; no acceptance claim.
2026-10-23 | Boyd | Row 298 (#1804, Q228), before implementation completion | Took over Megavolt 7a141269c on boyd/row298 at 45dca69f6 under mos-1496/1502. Approved D1-D6 govern human-only grants.scope.set, empty fallback, in-scope close/evidence and spend recheck. All inherited code UNTESTED; no Node/tests until Sage CLEAR. Plan and static gaps recorded in agents/boyd/work/queue-298/BUILD.md; no live grant or credential access.
2026-10-23 | Boyd | Row 298 (#1804, Q228), targeted verification | On sage-3642 T1 lane B, five whole files serial: business scope 7/0, bus scope 14/0, scope runtime 1/0, CLI scope 4/0, project runtime 3/0 (29/0, no skips). Initial bus fixture typo recorded and corrected. M1-M10 killed by named assertions, source restored and final files green. Dewey dewey-0933 positive control and notes addressed; manifest and evidence in agents/boyd/work/queue-298/BUILD.md. Full package/script gate remains Sage's. No live scope set.
2026-10-23T22:41:07Z | Boyd | Row 298 (#1804), rebase after row 239 | Rebased onto 833ec1cc8d7c11842efb82e57c1a0b256c8d22d7 on boyd/row298-r2 without altering published boyd/row298. Only append-log conflicts; upstream bytes preserved and Boyd suffixes appended. Product merges automatic; row 239 writer-lock revoke and post-revoke error handling preserved, organization.mjs unchanged from new base. Static checks only; UNTESTED on new base pending Sage CLEAR. Details in Boyd BUILD.md.
2026-10-23 | Goldie | Row 322 / #1818 F8/F9 before | The library installer runs the clone importer, whose yaml dependency is absent on fresh clones, and tail -n 1 hides uncaught errors behind Node's version banner. Plan: run the same-commit copy importer, include it in COPY_PATHS, keep the first error line with last-line fallback, append isolated regression coverage. Mos-1550 approves setup-only world(t) additions and the one-path copy-file pin addition. No tests run yet; Sage slot required.
2026-10-23 | Goldie | Row 322 / #1818 F8/F9 after | Importer now runs from the same-commit dependency-equipped copy; COPY_PATHS includes it. One-line refusal selects an actual Error/ERR_ line, avoiding Node's source excerpt/banner, with last-line fallback. Mos-1550-approved fixture setup and one-file pin changes; two append-only regression tests. Under sage-3808 CLEAR, serialized timeout -k 10 runs in /mnt/storage/scratch/tmp/goldieuQ2664: install26/26, copy18/18, pinned-copy10/10; new tests2/2 first run and restored; N installer expected-red2/2, F8 and F9 mutants killed, installer SHA256 restored each time. No unexpected red. Evidence and exact old-test changes in agents/goldie/work/queue-322/BUILD.md; full gate and Morgana review belong to Sage.
2026-10-23 | Goldie | Row 324 / #1820 before (Q247, mos-1555) | New branch goldie/row324 at row298 approved860afc2b6. Design/readers audit in agents/goldie/work/queue-324/BUILD.md: normalize omitted tracker to null, preserve present validation, guard sync readers/adapter config; runtime/instance-settings/broker checks already optional. Existing business.test.mjs98 explicitly requires tracker, so implementation held for Mos approval of removing that one list entry; no old test or product edit, no tests run.
2026-10-23 | Goldie | Row 324 / #1820 draft built (sage-3836, mos-1556) | Claim rev3759. Omitted tracker normalizes null; present including null/malformed stays validated. Loader, business validate, bootConfig trackerFor and Console sync reader handle absence. Row298's guarded runtime/instance-settings/broker paths preserved. Two new test files (9 planned tests); sole old-test edit drops tracker from required-key list under mos-1556. node --check6/6 and diff check pass. No tests run: untested branch push expressly ordered before CLEAR; Duckworth review and Sage gate pending.
2026-10-23 | Goldie | Row 324 / #1820 rebased verification (sage-3850) | Rebased onto3569bf54f as7e59ba8b3; only append-log conflicts, eight manifest files byte-equal. Serialized timeout-k10 scratch TMPDIR goldieOdgIjr: new business5/5, new CLI4/4 (includes Console absent read), existing business21/21; presence mutant killed, bytes restored, new files rerun5/5 and4/4. No unexpected red. Published rebased branch goldie/row324-r2 preserves original remote draft without force-push. Evidence/mutant diff/reader list in BUILD.md; review/full gate pending.
2026-10-23 | Goldie | Row 323 / #1819 before | Case 1 lacks a committed business file. Brief requires actual .53 values and refuses guessing. New branch goldie/row323 from local refactor da537e7d1; source review shows project root, literal human id, tracker sync identity/reference metadata and launch model values absent from DESIGN, plus nonempty launch.instances requires another role. Asked Sage (goldie-0008); no config or test written, no test run. Details agents/goldie/work/queue-323/BUILD.md.
2026-10-23 | Goldie | Row 323 / #1819 static blocker (sage-3823) | Mos-1554 requires no bot and an absent, never-created tracker token path. Static tracing confirms this refuses install validation (business cli84-97, credentials78-83) and bus boot (runtime131/157, bus credentials225-228), even without tracker.baseUrl; unset env also refuses at bus credentials278. Reported Sage goldie-0009; no JSON/test/credential written, no tests. Accepted source values/role and refusal-test rulings are recorded in BUILD.md; waiting for product-boundary resolution.
2026-10-24 | Goldie | Row 323 / #1819 resumed draft (sage-3859) | Rebasing prior notes onto Q247 landing dd6cb5872 resolved only append logs. Added trackerless .53 factory JSON: jason, approved project root, chief+assistant for required nonempty launch.instances, claude-code/Opus5.5/high, tester on. No credentials or token placeholder. Seven new tests prepare exact-doc loading plus scratch real validate/install/boot and three refusal paths; one restoring tester-switch mutant prepared. No old test edits; no tests before Sage CLEAR. Source choices/rulings in BUILD.md.
2026-10-24 | Goldie | Row 323 / #1819 pre-test commit (sage-3861 CLEAR) | Sage explicitly requests commit before runs; JSON/new7-case test/restoring mutant and field provenance committed untested before slot work. Exact sources distinguish mos-1554 human/no-bot intent, sage-3820 roles, sage-3822 project/model, and Q247 tracker omission. No old test edits. Next runs: new file, business whole, CLI whole, mutant/restored file, serialized scratch TMPDIR timeout-k10.
2026-10-24 | Goldie | Row 323 / #1819 first slot STOP (sage-3861) | On committed571376c30: new file7/7 and business261/261 green; whole CLI hit unchanged revoke-window.test.mjs170 (test579, row306 R4), no RESULT after20.1s at ask167/Promise.all194. Stopped suite on observing red, cleaned only positively identified own scratch fixture. Partial631 pass/1 fail before termination, not full-suite green. No mutant/restored run, no old-test edit/rerun. Manifest unchanged; Sage notified goldie-0016 and slot returned. Evidence local under queue-323, awaits ruling.
2026-10-24 | Goldie | Row 323 / #1819 second red STOP (mos-1560, sage-3864) | Isolated revoke-window whole-file run1 on unchanged571376c30 failed0/1 after20.126s, same no RESULT at ask167/test194. Start00:24:22Z, load1.46/1.68/2.70, busyhelperCLEAR, fresh scratch goldieBObDQ9. No run2/3/CLI retry/mutant/restored run; no source/old-test edit. Sage goldie-0018 has log/load/error; slot returned, awaits base-dd6cb5872 root cause. Remote remains unverified571.
2026-10-24 | Goldie | Row 323 / #1819 accepted evidence (mos-1561, sage-3869) | Sage traced revoke-window reds to37-byte TMPDIR making109-byte tools.sock >107 limit (session.mjs278), not Codex. Fresh31-byte /scratch/tmp/XXXXXX confirmation passes1/1 in0.84s. On unchanged571376c30 serialized timeout-k10: business261/261, new7/7, tester-switch mutant killed/restored SHA85e6b9ac, restored new7/7. Every log starts UTC/load/helperCLEAR; all positive runs0 fail/skip/cancel. CLI whole acceptance comes from Sage gate under mos-1561; no local CLI rerun. Evidence-only notes/log commit follows, no old test/source changes.
2026-10-24 | Goldie | Row325 / #1821 before (mos-1563/1564) | Real .mosaic/project.json idstack disagrees with Case1 project's mosaic-stack key; row323 scratch fixture omitted project file and only warned. New goldie/row325 at99930362e changes only that JSON key and expressly authorized old assertion key; new real-byte project-copy validation tests require valid and reject old key. Mutant prepared; no tests before Sage CLEAR, no queue writes/host actions.
2026-10-24 | Goldie | Row325 / #1821 after (sage-3882 CLEAR) | Only Case1 projects key becomesstack; only old test70 expected key updated under mos-1564. New3-case test copies real .mosaic/project.json bytes, requires CLI project valid, rejects old key. Serialized shortTMP timeout-k10: new3/3, business264/264, old-key mutant killed/restored SHA9e43bfef, restored new3/3; all positive0 fail/skip/cancel. Manifest4/4 verifies including unchanged project document. Logs/load lines and mutant diff in BUILD.md; no queue writes/host action, Gladstone review/Sage gate pending.
2026-10-24 | Bentina | Row157 / #1652 before (sage-3906, Q246, mos-1566) | Recovered seat/brief/design at ec86f2c84 on bentina/row157. Canonical queue next flags stale brief pin. Outbound audit finds broker gitea-grant node:https and web DNS-pinned node:https paths, triggering I2 explicit stop rule. Sage notified bentina-0001; BUILD.md records shifted cited sites. No implementation/test/Docker/credential/live-state action; awaiting lead re-pin and transport ruling.
2026-10-24 | Bentina | Row157 / #1652 first slot STOP (sage-3910) | New config/helper31/31 and proxy11/11 green. test-config exit1:55 pass/1 old output regression; new install20/20 green. Validator added absent install key to JSON. Stopped before Docker/install suite; slot returned Sage bentina-0004. sage-3916 authorizes preserving absent output shape/defaulting env to local: fixed own loader/new test only, no rerun before next CLEAR. test-config diff +4/-0. No commit/push; seat BUILD and01/02/03 logs.
2026-10-24 | Bentina | Row157 / #1652 after (sage-3919, sage-3923) | Absent install JSON shape preserved; helper defaults local. Existing config56/56, helper/config31/31, final proxy12/12, final test-install exit0 with three release0.0.12 rootless builds/pins, internal TLS/deny/DNS checks and real Vikunja publicGET200/notifier REST dummyGET401 through services proxy. Upstream-reset correction verified. Slot returned bentina-0007; no broader suites by Sage direction. Boyd then Darkwing review, Mos provider-host measurement and Sage landing gate pending. Evidence in seat BUILD and04-08 logs; only bentina/row157 candidate push authorized.
2026-10-24 | Bentina | Row157 / #1652 round2 before (boyd-0023, sage-3933) | Read reproduced B1 half-open refusal socket retention and B2 same-PID exec inherited cache bypass. Added two new regression files; no existing assertion edits. Proxy drains then destroys refused sockets with1s fallback; helper clears inherited cache/export attributes at source. No tests before CLEAR; Goldie holds slot. Source/causes recorded in seat BUILD.
2026-10-24 | Bentina | Row157 / #1652 round2 after (sage-3936) | B1 refused sockets now drain/destroy with1s bound; B2 source clears inherited cache/export attributes. Archived a14+new regressions red as intended (5controls pass/7defects fail), fixed12/12, helper/config33/33, egress22/22, rootless install exit0 incl image pins/network/services dummyGET. Logs09-13 in seat work. No existing test/assertion edit for round2. Slot returned bentina-0014; followup own-branch commit/manifest for Boyd re-review; Mos and Sage landing gates pending.
Before: agents can read the library; supporting-path and push-credential dependencies are separate unlanded heads. After intended: per-seat supporting writes/deletes with exact notices, human revert and one commit per Console save. Work is on bubba/row288, chained on original 283/285/286 commits; 284 tool base still pending. Plan and acceptance evidence: agents/bubba/work/queue-288/BUILD.md. Untested; no Node/test/build/gate run before Sage CLEAR. Landing waits for every dependency's own gate.
-10
View File
@@ -76,16 +76,6 @@ Rules enforced by `scripts/mosaic-config.mjs`:
Run paths (`build/hello/verify/reset`) fail closed when configuration is missing or invalid; they never invent it.
The optional `install` key selects the containerized install explicitly:
`"install": {"mode":"containerized","endpoint":"unix:///run/user/1000/docker.sock"}`.
An absent key defaults to local mode and remains absent in validation output.
Local mode forbids `endpoint`;
containerized mode requires a Unix socket endpoint without whitespace
(the actual pattern is `^unix:///[^\s]+\.sock$`). Unknown install keys or
modes refuse with exit 2 without changing the config. Validation contacts no
daemon; the endpoint helper checks rootless status before engine use. Keep
existing live configs unchanged until the install rollout is authorized.
## Missions & tasks (M2)
Missions and tasks are validated JSON data (strict schemas, version-pinned). The M2 layer is host-side only: mission directives are recorded for provenance but do not yet reach the runtime system prompt (capability/policy layer comes later).
-8
View File
@@ -1,8 +0,0 @@
# Beakley
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 217 (C2-06, handed over from Launchpad).
No launcher in this directory. The seat runs as a T3 thread (thread 88b97f65) and works in its own worktree. It has no
credentials of its own; it pushes only its own `beakley/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-10
View File
@@ -1,10 +0,0 @@
# Bentina
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1566. Owns row 157, I2: the install images, the
install config key, the endpoint helper and the egress proxy (docs/plans/2026-10-18_containerized-install-rows.md),
reassigned from Researcher so the coding runs on Codex.
No launcher in this directory. The seat runs as a T3 thread and works in its own worktree. It has no credentials of its
own; it pushes only its own `bentina/*` branches through the repository credential helper (mos-0981), never `refactor`,
`main` or another seat's branch. Claude seats orchestrate and review the install path (mos-1542 rule 4). Registered by
Sage on 2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
@@ -1,43 +0,0 @@
2026-10-24T04:05:53Z
23:05:53 up 10 days, 14:25, 2 users, load average: 2.29, 2.31, 1.89
CLEAR sage-3910 TMPDIR=/mnt/storage/scratch/tmp/lEvgx1
✔ install: absent (28.959485ms)
✔ install: local (24.660305ms)
✔ install: local endpoint (26.210523ms)
✔ install: containerized missing endpoint (25.495263ms)
✔ install: bad endpoint "tcp://localhost:2375" (28.62092ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (32.126399ms)
✔ install: bad endpoint "run/docker.sock" (26.69995ms)
✔ install: bad endpoint "unix://relative.sock" (33.36326ms)
✔ install: bad endpoint "unix:///a b.sock" (30.579866ms)
✔ install: bad endpoint "unix:///a.sock\n" (40.844622ms)
✔ install: bad endpoint "unix:///a" (36.371215ms)
✔ install: bad endpoint "" (31.603044ms)
✔ install: bad endpoint null (29.645166ms)
✔ install: bad endpoint 4 (30.638453ms)
✔ install: valid endpoint (28.088952ms)
✔ install: unknown mode (28.182929ms)
✔ install: missing mode (26.297287ms)
✔ install: unknown key (27.205488ms)
✔ install: null (29.189475ms)
✔ install: array (30.347371ms)
✔ helper refuses rootful (76.082505ms)
✔ helper refuses missing (58.354463ms)
✔ helper refuses substring (86.830586ms)
✔ helper refuses object (108.0825ms)
✔ helper refuses invalid JSON (85.203357ms)
✔ helper refuses null success (85.225204ms)
✔ helper refuses rootless output but failed (67.840402ms)
✔ helper checks once and explicitly names host for both calls (119.763305ms)
✔ local mode refuses before Docker null (35.42747ms)
✔ local mode refuses before Docker {"mode":"local"} (30.531767ms)
✔ invalid config refuses before Docker (30.092363ms)
ℹ tests 31
ℹ suites 0
ℹ pass 31
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 833.790665
EXIT=0
@@ -1,23 +0,0 @@
2026-10-24T04:06:01Z
23:06:01 up 10 days, 14:25, 2 users, load average: 2.27, 2.30, 1.89
CLEAR sage-3910 TMPDIR=/mnt/storage/scratch/tmp/lfgV9z
✔ allows exact host and port, fragmented headers, and preserves tunnel head (21.273322ms)
✔ unlisted host (1.324735ms)
✔ unlisted port (1.081626ms)
✔ GET (0.948442ms)
✔ invalid port (0.95243ms)
✔ userinfo (0.874803ms)
✔ bad header (0.782898ms)
✔ slow client has a total header deadline (81.829907ms)
✔ oversized header refuses (1.267008ms)
✔ missing, invalid and unknown lists fail closed (0.491274ms)
✔ refused upstream returns 502 while retaining one allowed decision (1.716299ms)
ℹ tests 11
ℹ suites 0
ℹ pass 11
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 156.342618
EXIT=0
@@ -1,90 +0,0 @@
2026-10-24T04:06:08Z
23:06:08 up 10 days, 14:25, 2 users, load average: 2.53, 2.36, 1.91
CLEAR sage-3910 TMPDIR=/mnt/storage/scratch/tmp/6eJjJz
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK valid console.sso loads and is in the output
OK console.sso HS256 alg exits 2 naming the path (exit 2)
OK console.sso alg none exits 2 naming the path (exit 2)
OK console.sso missing slot field exits 2 naming the path (exit 2)
OK console.sso slot name outside the pattern exits 2 naming the path (exit 2)
OK unknown key under console exits 2 naming the path (exit 2)
OK valid federation key loads and is in the output
OK federation unknown sub-key exits 2 naming the key (exit 2)
OK federation peer unknown sub-key exits 2 naming the key (exit 2)
OK federation peer origin over http exits 2 naming the key (exit 2)
OK federation peer origin with a path exits 2 naming the key (exit 2)
OK federation peer origin with userinfo exits 2 naming the key (exit 2)
OK federation peer id equal to self exits 2 naming the key (exit 2)
OK federation peer label empty exits 2 naming the key (exit 2)
OK federation not an object exits 2 naming the key (exit 2)
OK federation self pattern exits 2 naming the key (exit 2)
OK federation peers not an array exits 2 naming the key (exit 2)
OK federation peer not an object exits 2 naming the key (exit 2)
OK federation peer id pattern exits 2 naming the key (exit 2)
OK federation peer label over 80 characters exits 2 naming the key (exit 2)
OK federation duplicate peer id exits 2 naming the key (exit 2)
OK federation peer origin with a trailing slash exits 2 naming the key (exit 2)
OK federation self missing exits 2 naming the key (exit 2)
OK federation peer id missing exits 2 naming the key (exit 2)
OK federation peer label missing exits 2 naming the key (exit 2)
OK config without federation loads and has no federation output
FAIL config without console loads with unchanged output
OK compactionReadBytes 8388608 loads and is in the output as written
OK compactionReadBytes "x" loads and is in the output as written
OK compactionStreams 2048 loads and is in the output as written
OK compactionStreams "x" loads and is in the output as written
OK failed validation modified nothing
✔ install: absent (28.022711ms)
✔ install: local (26.314077ms)
✔ install: local endpoint (27.030768ms)
✔ install: containerized missing endpoint (26.036164ms)
✔ install: bad endpoint "tcp://localhost:2375" (29.311242ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (28.012086ms)
✔ install: bad endpoint "run/docker.sock" (26.941261ms)
✔ install: bad endpoint "unix://relative.sock" (26.90999ms)
✔ install: bad endpoint "unix:///a b.sock" (23.646272ms)
✔ install: bad endpoint "unix:///a.sock\n" (26.383344ms)
✔ install: bad endpoint "unix:///a" (24.914913ms)
✔ install: bad endpoint "" (24.320663ms)
✔ install: bad endpoint null (25.688262ms)
✔ install: bad endpoint 4 (26.934437ms)
✔ install: valid endpoint (27.504294ms)
✔ install: unknown mode (27.111833ms)
✔ install: missing mode (26.365767ms)
✔ install: unknown key (26.974983ms)
✔ install: null (25.910901ms)
✔ install: array (25.507423ms)
ℹ tests 20
ℹ suites 0
ℹ pass 20
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 584.03015
OK install configuration cases
selftest: 55 passed, 1 failed
EXIT=1
@@ -1,43 +0,0 @@
2026-10-24T04:08:52Z
23:08:52 up 10 days, 14:28, 2 users, load average: 2.16, 2.49, 2.04
CLEAR sage-3919 TMPDIR=/mnt/storage/scratch/tmp/o2n7Db
✔ install: absent (31.8592ms)
✔ install: local (29.807282ms)
✔ install: local endpoint (24.009896ms)
✔ install: containerized missing endpoint (26.296453ms)
✔ install: bad endpoint "tcp://localhost:2375" (26.404525ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (27.486365ms)
✔ install: bad endpoint "run/docker.sock" (24.280876ms)
✔ install: bad endpoint "unix://relative.sock" (24.363468ms)
✔ install: bad endpoint "unix:///a b.sock" (24.920898ms)
✔ install: bad endpoint "unix:///a.sock\n" (25.784031ms)
✔ install: bad endpoint "unix:///a" (27.89803ms)
✔ install: bad endpoint "" (28.939674ms)
✔ install: bad endpoint null (26.129602ms)
✔ install: bad endpoint 4 (27.601233ms)
✔ install: valid endpoint (29.310223ms)
✔ install: unknown mode (30.307014ms)
✔ install: missing mode (26.432375ms)
✔ install: unknown key (29.208115ms)
✔ install: null (29.793ms)
✔ install: array (28.956899ms)
✔ helper refuses rootful (88.440017ms)
✔ helper refuses missing (49.868469ms)
✔ helper refuses substring (76.03165ms)
✔ helper refuses object (77.494513ms)
✔ helper refuses invalid JSON (78.525159ms)
✔ helper refuses null success (79.166999ms)
✔ helper refuses rootless output but failed (52.195589ms)
✔ helper checks once and explicitly names host for both calls (124.380922ms)
✔ local mode refuses before Docker null (28.199092ms)
✔ local mode refuses before Docker {"mode":"local"} (29.618143ms)
✔ invalid config refuses before Docker (27.543884ms)
ℹ tests 31
ℹ suites 0
ℹ pass 31
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 762.528877
EXIT=0
@@ -1,90 +0,0 @@
2026-10-24T04:09:01Z
23:09:01 up 10 days, 14:28, 2 users, load average: 2.28, 2.50, 2.05
CLEAR sage-3919 TMPDIR=/mnt/storage/scratch/tmp/hYypHU
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK valid console.sso loads and is in the output
OK console.sso HS256 alg exits 2 naming the path (exit 2)
OK console.sso alg none exits 2 naming the path (exit 2)
OK console.sso missing slot field exits 2 naming the path (exit 2)
OK console.sso slot name outside the pattern exits 2 naming the path (exit 2)
OK unknown key under console exits 2 naming the path (exit 2)
OK valid federation key loads and is in the output
OK federation unknown sub-key exits 2 naming the key (exit 2)
OK federation peer unknown sub-key exits 2 naming the key (exit 2)
OK federation peer origin over http exits 2 naming the key (exit 2)
OK federation peer origin with a path exits 2 naming the key (exit 2)
OK federation peer origin with userinfo exits 2 naming the key (exit 2)
OK federation peer id equal to self exits 2 naming the key (exit 2)
OK federation peer label empty exits 2 naming the key (exit 2)
OK federation not an object exits 2 naming the key (exit 2)
OK federation self pattern exits 2 naming the key (exit 2)
OK federation peers not an array exits 2 naming the key (exit 2)
OK federation peer not an object exits 2 naming the key (exit 2)
OK federation peer id pattern exits 2 naming the key (exit 2)
OK federation peer label over 80 characters exits 2 naming the key (exit 2)
OK federation duplicate peer id exits 2 naming the key (exit 2)
OK federation peer origin with a trailing slash exits 2 naming the key (exit 2)
OK federation self missing exits 2 naming the key (exit 2)
OK federation peer id missing exits 2 naming the key (exit 2)
OK federation peer label missing exits 2 naming the key (exit 2)
OK config without federation loads and has no federation output
OK config without console loads with unchanged output
OK compactionReadBytes 8388608 loads and is in the output as written
OK compactionReadBytes "x" loads and is in the output as written
OK compactionStreams 2048 loads and is in the output as written
OK compactionStreams "x" loads and is in the output as written
OK failed validation modified nothing
✔ install: absent (33.602997ms)
✔ install: local (27.76628ms)
✔ install: local endpoint (28.360207ms)
✔ install: containerized missing endpoint (26.872508ms)
✔ install: bad endpoint "tcp://localhost:2375" (27.729466ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (29.663768ms)
✔ install: bad endpoint "run/docker.sock" (28.875798ms)
✔ install: bad endpoint "unix://relative.sock" (27.427064ms)
✔ install: bad endpoint "unix:///a b.sock" (30.77649ms)
✔ install: bad endpoint "unix:///a.sock\n" (34.03786ms)
✔ install: bad endpoint "unix:///a" (31.459666ms)
✔ install: bad endpoint "" (26.221924ms)
✔ install: bad endpoint null (31.982463ms)
✔ install: bad endpoint 4 (34.408275ms)
✔ install: valid endpoint (30.184118ms)
✔ install: unknown mode (26.58596ms)
✔ install: missing mode (26.563043ms)
✔ install: unknown key (28.663535ms)
✔ install: null (26.567284ms)
✔ install: array (24.756494ms)
ℹ tests 20
ℹ suites 0
ℹ pass 20
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 640.201326
OK install configuration cases
selftest: 56 passed, 0 failed
EXIT=0
@@ -1,245 +0,0 @@
2026-10-24T04:09:14Z
23:09:14 up 10 days, 14:29, 2 users, load average: 2.58, 2.56, 2.08
CLEAR sage-3919 TMPDIR=/mnt/storage/scratch/tmp/7xUNh5
✔ install: absent (28.837858ms)
✔ install: local (28.328967ms)
✔ install: local endpoint (26.178892ms)
✔ install: containerized missing endpoint (25.578947ms)
✔ install: bad endpoint "tcp://localhost:2375" (24.988885ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (25.850327ms)
✔ install: bad endpoint "run/docker.sock" (26.499708ms)
✔ install: bad endpoint "unix://relative.sock" (42.276491ms)
✔ install: bad endpoint "unix:///a b.sock" (32.161423ms)
✔ install: bad endpoint "unix:///a.sock\n" (26.834457ms)
✔ install: bad endpoint "unix:///a" (30.552354ms)
✔ install: bad endpoint "" (28.938768ms)
✔ install: bad endpoint null (27.00598ms)
✔ install: bad endpoint 4 (29.63046ms)
✔ install: valid endpoint (28.707851ms)
✔ install: unknown mode (25.507911ms)
✔ install: missing mode (27.101142ms)
✔ install: unknown key (32.316453ms)
✔ install: null (27.273355ms)
✔ install: array (25.503395ms)
✔ helper refuses rootful (81.447175ms)
✔ helper refuses missing (49.32616ms)
✔ helper refuses substring (80.299191ms)
✔ helper refuses object (96.465808ms)
✔ helper refuses invalid JSON (89.854499ms)
✔ helper refuses null success (79.934412ms)
✔ helper refuses rootless output but failed (61.053445ms)
✔ helper checks once and explicitly names host for both calls (120.469053ms)
✔ local mode refuses before Docker null (28.252291ms)
✔ local mode refuses before Docker {"mode":"local"} (32.631557ms)
✔ invalid config refuses before Docker (28.529643ms)
ℹ tests 31
ℹ suites 0
ℹ pass 31
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 798.423992
✔ allows exact host and port, fragmented headers, and preserves tunnel head (18.932222ms)
✔ unlisted host (1.309946ms)
✔ unlisted port (1.067401ms)
✔ GET (0.988233ms)
✔ invalid port (0.919738ms)
✔ userinfo (0.832286ms)
✔ bad header (0.761085ms)
✔ slow client has a total header deadline (80.345593ms)
✔ oversized header refuses (1.044536ms)
✔ missing, invalid and unknown lists fail closed (0.486134ms)
✔ refused upstream returns 502 while retaining one allowed decision (1.562662ms)
ℹ tests 11
ℹ suites 0
ℹ pass 11
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 149.849801
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 454B done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#2 DONE 1.6s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
#4 ...
#5 [internal] load build context
#5 transferring context: 31.36MB 0.1s done
#5 DONE 0.2s
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 sha256:08489011619dfda982191cb9cc0418dc48ea3b1341588aeef534e4db0ab4d3d0 447B / 447B 0.1s done
#4 sha256:1a0cf4b1ffc8dd659bb8c6e90471bc8fec24620e4fdb68bb2b6faa56154ae146 0B / 1.71MB 0.2s
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 0B / 50.86MB 0.2s
#4 sha256:517d640e32835287653c4b15eb5d9f6bcf48e103baea4f7e29bac937002f649b 3.31kB / 3.31kB 0.2s done
#4 sha256:1a0cf4b1ffc8dd659bb8c6e90471bc8fec24620e4fdb68bb2b6faa56154ae146 1.71MB / 1.71MB 0.3s done
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 3.15MB / 28.25MB 0.3s
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 6.29MB / 50.86MB 0.5s
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 11.53MB / 28.25MB 0.5s
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 14.68MB / 50.86MB 0.6s
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 19.92MB / 28.25MB 0.6s
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 24.12MB / 50.86MB 0.8s
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 28.25MB / 28.25MB 0.8s done
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 49.28MB / 50.86MB 1.1s
#4 extracting sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 50.86MB / 50.86MB 1.1s done
#4 extracting sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 0.7s done
#4 DONE 1.6s
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 extracting sha256:517d640e32835287653c4b15eb5d9f6bcf48e103baea4f7e29bac937002f649b 0.0s done
#4 extracting sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a
#4 extracting sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 1.0s done
#4 DONE 2.6s
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 extracting sha256:1a0cf4b1ffc8dd659bb8c6e90471bc8fec24620e4fdb68bb2b6faa56154ae146 0.0s done
#4 extracting sha256:08489011619dfda982191cb9cc0418dc48ea3b1341588aeef534e4db0ab4d3d0 done
#4 DONE 2.7s
#6 [2/9] WORKDIR /opt/mosaic
#6 DONE 0.0s
#7 [3/9] COPY package.json package-lock.json ./
#7 DONE 0.0s
#8 [4/9] RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
#8 1.008 npm warn deprecated [email protected]: Use your platform's native DOMException instead
#8 2.464
#8 2.464 added 128 packages in 2s
#8 2.465 npm notice
#8 2.465 npm notice New major version of npm available! 11.19.0 -> 12.2.1
#8 2.465 npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.1
#8 2.465 npm notice To update run: npm install -g [email protected]
#8 2.465 npm notice
#8 2.562 npm warn using --force Recommended protections disabled.
#8 DONE 2.7s
#9 [5/9] COPY packages/ packages/
#9 DONE 0.1s
#10 [6/9] COPY scripts/ scripts/
#10 DONE 0.0s
#11 [7/9] COPY contracts/ contracts/
#11 DONE 0.0s
#12 [8/9] COPY roles/ roles/
#12 DONE 0.0s
#13 [9/9] COPY RELEASE ./
#13 DONE 0.0s
#14 exporting to image
#14 exporting layers
#14 exporting layers 3.3s done
#14 exporting manifest sha256:b288643bbe89b43f6ffe7890598fe70801933c9e1e5d64efbfc6d87bbc502389 done
#14 exporting config sha256:c8e137b79bfd807d4d9fc37d5ac211476decc3cc3c8db1e39b2e5dedf5471191 done
#14 exporting attestation manifest sha256:5f2e757dd0fc2a424c78eb4bdd2ddd3039067afb54bc76bc03d6a6f9f1e2ac75 done
#14 exporting manifest list sha256:7b4fa741d785225148156d1962a1770b40ab84b55ce8ffde5a3485b316f93b3a done
#14 naming to docker.io/library/mosaic-core:0.0.12 done
#14 unpacking to docker.io/library/mosaic-core:0.0.12
#14 unpacking to docker.io/library/mosaic-core:0.0.12 1.6s done
#14 DONE 4.9s
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 424B done
#1 WARN: InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/mosaic-core:0.0.12
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/2] FROM docker.io/library/mosaic-core:0.0.12@sha256:7b4fa741d785225148156d1962a1770b40ab84b55ce8ffde5a3485b316f93b3a
#4 resolve docker.io/library/mosaic-core:0.0.12@sha256:7b4fa741d785225148156d1962a1770b40ab84b55ce8ffde5a3485b316f93b3a done
#4 DONE 0.0s
#5 [2/2] RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/[email protected] && npm cache clean --force && rm -rf /root/.npm /tmp/*
#5 1.716 npm warn deprecated [email protected]: Use your platform's native DOMException instead
#5 6.031
#5 6.031 added 167 packages in 6s
#5 6.032 npm warn install-scripts 4 packages have install scripts not yet covered by allowScripts:
#5 6.032 npm warn install-scripts @anthropic-ai/[email protected] (postinstall: node install.cjs)
#5 6.032 npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
#5 6.032 npm warn install-scripts [email protected] (postinstall: node install.js)
#5 6.032 npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
#5 6.032 npm warn install-scripts
#5 6.032 npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs --location=user` to allow them for all global installs.
#5 6.119 npm warn using --force Recommended protections disabled.
#5 DONE 6.3s
#6 exporting to image
#6 exporting layers
#6 exporting layers 18.7s done
#6 exporting manifest sha256:16af90e8e3291626f37fb5b2e78904d87126d875e7bcc240dd2ba453ca62bc10 done
#6 exporting config sha256:ad269f0a84c0b1c5833b787793a45df782c1aae86e37f6216782a12a01f4557b done
#6 exporting attestation manifest sha256:436490e0d139cb0db83f260c42ac3364377971160b2418cd03ee07c18873a5f6 done
#6 exporting manifest list sha256:bc95495952e58a005c275a9d00842242c17965d0d9c2a83c476048cbfc110946 done
#6 naming to docker.io/library/mosaic-session:0.0.12 done
#6 unpacking to docker.io/library/mosaic-session:0.0.12
#6 unpacking to docker.io/library/mosaic-session:0.0.12 5.3s done
#6 DONE 24.0s
1 warning found (use docker --debug to expand):
 - InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 299B done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/4] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
#4 DONE 0.0s
#5 [2/4] WORKDIR /opt/mosaic
#5 CACHED
#6 [internal] load build context
#6 transferring context: 459B 0.0s done
#6 DONE 0.0s
#7 [3/4] COPY packages/egress/ packages/egress/
#7 DONE 0.0s
#8 [4/4] COPY contracts/egress.json contracts/egress.json
#8 DONE 0.0s
#9 exporting to image
#9 exporting layers 0.0s done
#9 exporting manifest sha256:f3f318ddc871b5a5f953aa0709cf69194260e633e26b15daafefe40572a36f27 done
#9 exporting config sha256:c315f86a3be49d29aa7cad287a1653424206adb43683a34b91633b9bb8a14c6b done
#9 exporting attestation manifest sha256:d41ee39a55170821cf46236fbf6069519e52c7386e27259ee17113777fa348e1 done
#9 exporting manifest list sha256:af29f552efea177d706779ed9afb43c89763b58de913da0a570208cdda619d50 done
#9 naming to docker.io/library/mosaic-egress:0.0.12 done
#9 unpacking to docker.io/library/mosaic-egress:0.0.12 0.0s done
#9 DONE 0.1s
internal network: listed TLS reached, unlisted host/port refused, external DNS refused
Vikunja client public GET 200; notifier REST dummy-token GET 401
test-install: PASS (provider-host measurement remains Mos gate)
EXIT=0
@@ -1,24 +0,0 @@
2026-10-24T04:13:52Z
23:13:52 up 10 days, 14:33, 2 users, load average: 2.19, 2.69, 2.26
CLEAR sage-3923 TMPDIR=/mnt/storage/scratch/tmp/5Tw4Jn
✔ allows exact host and port, fragmented headers, and preserves tunnel head (17.065009ms)
✔ unlisted host (1.179696ms)
✔ unlisted port (4.182798ms)
✔ GET (0.916554ms)
✔ invalid port (0.897474ms)
✔ userinfo (0.772938ms)
✔ bad header (0.737254ms)
✔ slow client has a total header deadline (81.64527ms)
✔ oversized header refuses (1.073337ms)
✔ missing, invalid and unknown lists fail closed (0.464571ms)
✔ refused upstream returns 502 while retaining one allowed decision (1.555082ms)
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (21.757224ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 173.90579
EXIT=0
@@ -1,207 +0,0 @@
2026-10-24T04:13:56Z
23:13:56 up 10 days, 14:33, 2 users, load average: 2.18, 2.67, 2.26
CLEAR sage-3923 TMPDIR=/mnt/storage/scratch/tmp/Yb2lkZ
✔ install: absent (26.874578ms)
✔ install: local (25.86789ms)
✔ install: local endpoint (24.545469ms)
✔ install: containerized missing endpoint (26.458263ms)
✔ install: bad endpoint "tcp://localhost:2375" (24.315713ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (27.113397ms)
✔ install: bad endpoint "run/docker.sock" (24.399221ms)
✔ install: bad endpoint "unix://relative.sock" (26.259544ms)
✔ install: bad endpoint "unix:///a b.sock" (24.051655ms)
✔ install: bad endpoint "unix:///a.sock\n" (23.493287ms)
✔ install: bad endpoint "unix:///a" (25.527734ms)
✔ install: bad endpoint "" (24.647712ms)
✔ install: bad endpoint null (24.029319ms)
✔ install: bad endpoint 4 (22.545369ms)
✔ install: valid endpoint (24.217907ms)
✔ install: unknown mode (22.578207ms)
✔ install: missing mode (26.668685ms)
✔ install: unknown key (25.239503ms)
✔ install: null (26.507504ms)
✔ install: array (23.325961ms)
✔ helper refuses rootful (83.142099ms)
✔ helper refuses missing (48.938896ms)
✔ helper refuses substring (74.574145ms)
✔ helper refuses object (69.21506ms)
✔ helper refuses invalid JSON (71.320245ms)
✔ helper refuses null success (70.397988ms)
✔ helper refuses rootless output but failed (50.077162ms)
✔ helper checks once and explicitly names host for both calls (116.70882ms)
✔ local mode refuses before Docker null (26.523468ms)
✔ local mode refuses before Docker {"mode":"local"} (26.276186ms)
✔ invalid config refuses before Docker (26.092328ms)
ℹ tests 31
ℹ suites 0
ℹ pass 31
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 707.332707
✔ allows exact host and port, fragmented headers, and preserves tunnel head (15.694598ms)
✔ unlisted host (1.162772ms)
✔ unlisted port (1.047448ms)
✔ GET (0.913924ms)
✔ invalid port (0.930799ms)
✔ userinfo (0.750312ms)
✔ bad header (0.739214ms)
✔ slow client has a total header deadline (81.348784ms)
✔ oversized header refuses (0.992296ms)
✔ missing, invalid and unknown lists fail closed (0.441271ms)
✔ refused upstream returns 502 while retaining one allowed decision (1.4408ms)
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (21.481904ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 169.285852
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 454B done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
#4 DONE 0.0s
#5 [internal] load build context
#5 transferring context: 31.37MB 0.1s done
#5 DONE 0.1s
#6 [3/9] COPY package.json package-lock.json ./
#6 CACHED
#7 [2/9] WORKDIR /opt/mosaic
#7 CACHED
#8 [4/9] RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
#8 CACHED
#9 [5/9] COPY packages/ packages/
#9 DONE 0.1s
#10 [6/9] COPY scripts/ scripts/
#10 DONE 0.0s
#11 [7/9] COPY contracts/ contracts/
#11 DONE 0.0s
#12 [8/9] COPY roles/ roles/
#12 DONE 0.0s
#13 [9/9] COPY RELEASE ./
#13 DONE 0.0s
#14 exporting to image
#14 exporting layers
#14 exporting layers 0.4s done
#14 exporting manifest sha256:065dd55d59585806e540929e2ea66df397d47292cc84160bb7be491ecd2b9d59 done
#14 exporting config sha256:3d97362dc6b6242fc540f08f538068b91ec28b1d3c9a34d7bc2bebff137a7a40 done
#14 exporting attestation manifest sha256:f66bf47e4ec5b67db64f44d8554192f646111116e165ec3bc017b5a2b011204b done
#14 exporting manifest list sha256:d840b0ec16ce6548549d18fb8bfc6b13c76ee77e9131a317582f919c61dcaf7f done
#14 naming to docker.io/library/mosaic-core:0.0.12 done
#14 unpacking to docker.io/library/mosaic-core:0.0.12
#14 unpacking to docker.io/library/mosaic-core:0.0.12 0.2s done
#14 DONE 0.6s
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 424B done
#1 WARN: InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/mosaic-core:0.0.12
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/2] FROM docker.io/library/mosaic-core:0.0.12@sha256:d840b0ec16ce6548549d18fb8bfc6b13c76ee77e9131a317582f919c61dcaf7f
#4 resolve docker.io/library/mosaic-core:0.0.12@sha256:d840b0ec16ce6548549d18fb8bfc6b13c76ee77e9131a317582f919c61dcaf7f done
#4 DONE 0.0s
#5 [2/2] RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/[email protected] && npm cache clean --force && rm -rf /root/.npm /tmp/*
#5 1.700 npm warn deprecated [email protected]: Use your platform's native DOMException instead
#5 5.518
#5 5.518 added 167 packages in 5s
#5 5.519 npm warn install-scripts 4 packages have install scripts not yet covered by allowScripts:
#5 5.519 npm warn install-scripts @anthropic-ai/[email protected] (postinstall: node install.cjs)
#5 5.519 npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
#5 5.519 npm warn install-scripts [email protected] (postinstall: node install.js)
#5 5.519 npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
#5 5.519 npm warn install-scripts
#5 5.519 npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs --location=user` to allow them for all global installs.
#5 5.590 npm warn using --force Recommended protections disabled.
#5 DONE 5.7s
#6 exporting to image
#6 exporting layers
#6 exporting layers 16.9s done
#6 exporting manifest sha256:d4c83e350da6f18bfa7f3dbcef9ab078d72ef7f1f957a2926726e17973969e6a done
#6 exporting config sha256:8afa310f48e06614963dcbcdc9c3ac497d9e4bad4ddd0524062ba8a52bc3cda2 done
#6 exporting attestation manifest sha256:b7f082a8cd17d2c90eb27a81359aea66186b80d56a6d0c5e89d29187a2f78ed4 done
#6 exporting manifest list sha256:2b6299e5a329adf85ea36038cb0407f2328e72e848dbfe95a17bd1fefbf9cf92 done
#6 naming to docker.io/library/mosaic-session:0.0.12 done
#6 unpacking to docker.io/library/mosaic-session:0.0.12
#6 unpacking to docker.io/library/mosaic-session:0.0.12 4.7s done
#6 DONE 21.7s
1 warning found (use docker --debug to expand):
 - InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 299B done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/4] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
#4 DONE 0.0s
#5 [internal] load build context
#5 transferring context: 459B 0.0s done
#5 DONE 0.0s
#6 [2/4] WORKDIR /opt/mosaic
#6 CACHED
#7 [3/4] COPY packages/egress/ packages/egress/
#7 DONE 0.0s
#8 [4/4] COPY contracts/egress.json contracts/egress.json
#8 DONE 0.0s
#9 exporting to image
#9 exporting layers 0.0s done
#9 exporting manifest sha256:e47c86064470f291d2ea468a1002f1e5f9b2e682e42940d06bd33d4e39945a6e done
#9 exporting config sha256:ba520eadeb66ccfa2dbd3170744fca08affb4dac377d6cf2d26ec27d98d4ee25 done
#9 exporting attestation manifest sha256:93cbd2f8a7fb1afb858b7b1a35a3020af8e9623e3a98be71d04e9d746c25d148 done
#9 exporting manifest list sha256:a55e9d5ec6da74933bbefac6816efd0ea5246e825f61eaa4297e7baf7b09501c done
#9 naming to docker.io/library/mosaic-egress:0.0.12 done
#9 unpacking to docker.io/library/mosaic-egress:0.0.12 0.0s done
#9 DONE 0.1s
internal network: listed TLS reached, unlisted host/port refused, external DNS refused
Vikunja client public GET 200; notifier REST dummy-token GET 401
test-install: PASS (provider-host measurement remains Mos gate)
EXIT=0
@@ -1,165 +0,0 @@
2026-10-24T04:29:25Z
23:29:25 up 10 days, 14:49, 2 users, load average: 2.63, 2.97, 2.72
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/jcNy2F archived=a14fdc443 expected RED
✔ refusal 408 releases server socket (allowHalfOpen=false) (62.262337ms)
✖ refusal 408 releases server socket (allowHalfOpen=true) (552.049114ms)
✔ refusal 400 releases server socket (allowHalfOpen=false) (12.304169ms)
✖ refusal 400 releases server socket (allowHalfOpen=true) (509.122366ms)
✔ refusal 403 releases server socket (allowHalfOpen=false) (11.566877ms)
✖ refusal 403 releases server socket (allowHalfOpen=true) (508.134074ms)
✔ refusal 431 releases server socket (allowHalfOpen=false) (11.339612ms)
✖ refusal 431 releases server socket (allowHalfOpen=true) (507.396552ms)
✔ refusal 502 releases server socket (allowHalfOpen=false) (11.812225ms)
✖ refusal 502 releases server socket (allowHalfOpen=true) (509.521901ms)
✖ inherited cache is discarded; private cache stays unexported (rootless=false) (49.005306ms)
✖ inherited cache is discarded; private cache stays unexported (rootless=true) (42.961333ms)
ℹ tests 12
ℹ suites 0
ℹ pass 5
ℹ fail 7
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2739.328587
✖ failing tests:
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
✖ refusal 408 releases server socket (allowHalfOpen=true) (552.049114ms)
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30: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: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
✖ refusal 400 releases server socket (allowHalfOpen=true) (509.122366ms)
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30: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: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
✖ refusal 403 releases server socket (allowHalfOpen=true) (508.134074ms)
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30: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: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
✖ refusal 431 releases server socket (allowHalfOpen=true) (507.396552ms)
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30: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: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
✖ refusal 502 releases server socket (allowHalfOpen=true) (509.521901ms)
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30: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: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:10:36
✖ inherited cache is discarded; private cache stays unexported (rootless=false) (49.005306ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 2
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:19:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 2,
operator: 'strictEqual',
diff: 'simple'
}
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:10:36
✖ inherited cache is discarded; private cache stays unexported (rootless=true) (42.961333ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
'--host',
+ 'unix:///inherited.sock',
+ 'ps'
- 'unix:///configured.sock',
- 'info',
- '--format',
- '{{json .SecurityOptions}}'
]
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:20:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ '--host', 'unix:///inherited.sock', 'ps' ],
expected: [ '--host', 'unix:///configured.sock', 'info', '--format', '{{json .SecurityOptions}}' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
EXIT=1 (expected 1 for reproduced defects)
@@ -1,24 +0,0 @@
2026-10-24T04:29:39Z
23:29:39 up 10 days, 14:49, 2 users, load average: 2.20, 2.85, 2.69
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/0gZQZl
✔ refusal 408 releases server socket (allowHalfOpen=false) (53.837502ms)
✔ refusal 408 releases server socket (allowHalfOpen=true) (42.030239ms)
✔ refusal 400 releases server socket (allowHalfOpen=false) (1.261482ms)
✔ refusal 400 releases server socket (allowHalfOpen=true) (0.904817ms)
✔ refusal 403 releases server socket (allowHalfOpen=false) (0.917847ms)
✔ refusal 403 releases server socket (allowHalfOpen=true) (0.804008ms)
✔ refusal 431 releases server socket (allowHalfOpen=false) (0.718814ms)
✔ refusal 431 releases server socket (allowHalfOpen=true) (0.668466ms)
✔ refusal 502 releases server socket (allowHalfOpen=false) (1.344911ms)
✔ refusal 502 releases server socket (allowHalfOpen=true) (0.979004ms)
✔ inherited cache is discarded; private cache stays unexported (rootless=false) (82.339935ms)
✔ inherited cache is discarded; private cache stays unexported (rootless=true) (112.830463ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 245.761199
EXIT=0
@@ -1,45 +0,0 @@
2026-10-24T04:29:49Z
23:29:49 up 10 days, 14:49, 2 users, load average: 2.09, 2.81, 2.68
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/tUdE4l
✔ install: absent (26.366604ms)
✔ install: local (22.63247ms)
✔ install: local endpoint (22.799393ms)
✔ install: containerized missing endpoint (25.600693ms)
✔ install: bad endpoint "tcp://localhost:2375" (23.03486ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (23.131519ms)
✔ install: bad endpoint "run/docker.sock" (24.109097ms)
✔ install: bad endpoint "unix://relative.sock" (23.350143ms)
✔ install: bad endpoint "unix:///a b.sock" (25.171936ms)
✔ install: bad endpoint "unix:///a.sock\n" (23.993281ms)
✔ install: bad endpoint "unix:///a" (23.480143ms)
✔ install: bad endpoint "" (24.103262ms)
✔ install: bad endpoint null (22.669641ms)
✔ install: bad endpoint 4 (24.019575ms)
✔ install: valid endpoint (24.603314ms)
✔ install: unknown mode (24.602796ms)
✔ install: missing mode (23.797819ms)
✔ install: unknown key (22.998491ms)
✔ install: null (25.129192ms)
✔ install: array (24.482115ms)
✔ helper refuses rootful (69.872106ms)
✔ helper refuses missing (48.024972ms)
✔ helper refuses substring (75.261764ms)
✔ helper refuses object (69.618809ms)
✔ helper refuses invalid JSON (69.071543ms)
✔ helper refuses null success (72.882659ms)
✔ helper refuses rootless output but failed (53.345431ms)
✔ helper checks once and explicitly names host for both calls (102.552473ms)
✔ local mode refuses before Docker null (26.011087ms)
✔ local mode refuses before Docker {"mode":"local"} (28.185416ms)
✔ invalid config refuses before Docker (27.514422ms)
✔ inherited cache is discarded; private cache stays unexported (rootless=false) (75.055951ms)
✔ inherited cache is discarded; private cache stays unexported (rootless=true) (109.227515ms)
ℹ tests 33
ℹ suites 0
ℹ pass 33
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 690.153576
EXIT=0
@@ -1,34 +0,0 @@
2026-10-24T04:29:59Z
23:29:59 up 10 days, 14:49, 2 users, load average: 1.93, 2.75, 2.66
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/TviERP
✔ allows exact host and port, fragmented headers, and preserves tunnel head (23.927026ms)
✔ unlisted host (1.732173ms)
✔ unlisted port (1.604846ms)
✔ GET (1.184716ms)
✔ invalid port (1.343703ms)
✔ userinfo (1.188622ms)
✔ bad header (1.150055ms)
✔ slow client has a total header deadline (81.793054ms)
✔ oversized header refuses (1.244104ms)
✔ missing, invalid and unknown lists fail closed (0.57805ms)
✔ refused upstream returns 502 while retaining one allowed decision (1.746501ms)
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (22.237884ms)
✔ refusal 408 releases server socket (allowHalfOpen=false) (55.691211ms)
✔ refusal 408 releases server socket (allowHalfOpen=true) (43.646383ms)
✔ refusal 400 releases server socket (allowHalfOpen=false) (1.568816ms)
✔ refusal 400 releases server socket (allowHalfOpen=true) (1.105107ms)
✔ refusal 403 releases server socket (allowHalfOpen=false) (1.202959ms)
✔ refusal 403 releases server socket (allowHalfOpen=true) (1.016559ms)
✔ refusal 431 releases server socket (allowHalfOpen=false) (0.914639ms)
✔ refusal 431 releases server socket (allowHalfOpen=true) (0.889516ms)
✔ refusal 502 releases server socket (allowHalfOpen=false) (1.698346ms)
✔ refusal 502 releases server socket (allowHalfOpen=true) (1.234793ms)
ℹ tests 22
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 183.862745
EXIT=0
@@ -1,219 +0,0 @@
2026-10-24T04:30:08Z
23:30:08 up 10 days, 14:49, 2 users, load average: 1.79, 2.69, 2.64
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/qmqk7B
✔ install: absent (25.729884ms)
✔ install: local (27.889152ms)
✔ install: local endpoint (24.35751ms)
✔ install: containerized missing endpoint (32.226985ms)
✔ install: bad endpoint "tcp://localhost:2375" (36.932763ms)
✔ install: bad endpoint "ssh://host/run/docker.sock" (30.311354ms)
✔ install: bad endpoint "run/docker.sock" (25.198808ms)
✔ install: bad endpoint "unix://relative.sock" (26.143708ms)
✔ install: bad endpoint "unix:///a b.sock" (26.182728ms)
✔ install: bad endpoint "unix:///a.sock\n" (24.78053ms)
✔ install: bad endpoint "unix:///a" (25.183076ms)
✔ install: bad endpoint "" (26.038913ms)
✔ install: bad endpoint null (28.122111ms)
✔ install: bad endpoint 4 (25.273681ms)
✔ install: valid endpoint (24.978162ms)
✔ install: unknown mode (25.362867ms)
✔ install: missing mode (30.700943ms)
✔ install: unknown key (25.157537ms)
✔ install: null (25.311198ms)
✔ install: array (22.791684ms)
✔ helper refuses rootful (73.335956ms)
✔ helper refuses missing (58.142601ms)
✔ helper refuses substring (76.536037ms)
✔ helper refuses object (71.066459ms)
✔ helper refuses invalid JSON (75.615304ms)
✔ helper refuses null success (74.72487ms)
✔ helper refuses rootless output but failed (56.316538ms)
✔ helper checks once and explicitly names host for both calls (116.375519ms)
✔ local mode refuses before Docker null (33.189488ms)
✔ local mode refuses before Docker {"mode":"local"} (33.416869ms)
✔ invalid config refuses before Docker (33.260841ms)
✔ inherited cache is discarded; private cache stays unexported (rootless=false) (77.918846ms)
✔ inherited cache is discarded; private cache stays unexported (rootless=true) (125.24254ms)
ℹ tests 33
ℹ suites 0
ℹ pass 33
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 751.025142
✔ allows exact host and port, fragmented headers, and preserves tunnel head (21.619769ms)
✔ unlisted host (1.449654ms)
✔ unlisted port (1.146495ms)
✔ GET (1.037357ms)
✔ invalid port (0.994228ms)
✔ userinfo (0.84581ms)
✔ bad header (0.834734ms)
✔ slow client has a total header deadline (81.37968ms)
✔ oversized header refuses (1.096351ms)
✔ missing, invalid and unknown lists fail closed (0.499339ms)
✔ refused upstream returns 502 while retaining one allowed decision (1.730862ms)
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (21.825124ms)
✔ refusal 408 releases server socket (allowHalfOpen=false) (55.028256ms)
✔ refusal 408 releases server socket (allowHalfOpen=true) (42.613145ms)
✔ refusal 400 releases server socket (allowHalfOpen=false) (1.348936ms)
✔ refusal 400 releases server socket (allowHalfOpen=true) (1.104981ms)
✔ refusal 403 releases server socket (allowHalfOpen=false) (1.097498ms)
✔ refusal 403 releases server socket (allowHalfOpen=true) (0.965577ms)
✔ refusal 431 releases server socket (allowHalfOpen=false) (0.806917ms)
✔ refusal 431 releases server socket (allowHalfOpen=true) (0.752832ms)
✔ refusal 502 releases server socket (allowHalfOpen=false) (1.582846ms)
✔ refusal 502 releases server socket (allowHalfOpen=true) (1.14509ms)
ℹ tests 22
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 181.752441
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 454B done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#2 DONE 0.7s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
#4 DONE 0.0s
#5 [internal] load build context
#5 transferring context: 31.38MB 0.1s done
#5 DONE 0.1s
#6 [2/9] WORKDIR /opt/mosaic
#6 CACHED
#7 [3/9] COPY package.json package-lock.json ./
#7 CACHED
#8 [4/9] RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
#8 CACHED
#9 [5/9] COPY packages/ packages/
#9 DONE 0.1s
#10 [6/9] COPY scripts/ scripts/
#10 DONE 0.0s
#11 [7/9] COPY contracts/ contracts/
#11 DONE 0.0s
#12 [8/9] COPY roles/ roles/
#12 DONE 0.0s
#13 [9/9] COPY RELEASE ./
#13 DONE 0.0s
#14 exporting to image
#14 exporting layers
#14 exporting layers 0.4s done
#14 exporting manifest sha256:cfd93bac400adc7580134f6177efd9d748e4b4ad65b0ff22515ca3b3ffafa530 done
#14 exporting config sha256:bda6c7420c2df9510f081d28f50d205083867d0ce907c12290fff226c020fe38 done
#14 exporting attestation manifest sha256:79c61e7daf7794a3db88121ce6260fafab69f182ef89773914c41ca75a44574c done
#14 exporting manifest list sha256:82fc7a3df060c3e58799cca4f89dbd57dc007c5534fdedce54d3de637cc83b67 done
#14 naming to docker.io/library/mosaic-core:0.0.12 done
#14 unpacking to docker.io/library/mosaic-core:0.0.12
#14 unpacking to docker.io/library/mosaic-core:0.0.12 0.2s done
#14 DONE 0.7s
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 424B done
#1 WARN: InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/mosaic-core:0.0.12
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/2] FROM docker.io/library/mosaic-core:0.0.12@sha256:82fc7a3df060c3e58799cca4f89dbd57dc007c5534fdedce54d3de637cc83b67
#4 resolve docker.io/library/mosaic-core:0.0.12@sha256:82fc7a3df060c3e58799cca4f89dbd57dc007c5534fdedce54d3de637cc83b67 done
#4 DONE 0.0s
#5 [2/2] RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/[email protected] && npm cache clean --force && rm -rf /root/.npm /tmp/*
#5 1.920 npm warn deprecated [email protected]: Use your platform's native DOMException instead
#5 6.054
#5 6.054 added 167 packages in 6s
#5 6.055 npm warn install-scripts 4 packages have install scripts not yet covered by allowScripts:
#5 6.055 npm warn install-scripts @anthropic-ai/[email protected] (postinstall: node install.cjs)
#5 6.055 npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
#5 6.055 npm warn install-scripts [email protected] (postinstall: node install.js)
#5 6.055 npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
#5 6.055 npm warn install-scripts
#5 6.055 npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs --location=user` to allow them for all global installs.
#5 6.127 npm warn using --force Recommended protections disabled.
#5 DONE 6.3s
#6 exporting to image
#6 exporting layers
#6 exporting layers 19.5s done
#6 exporting manifest sha256:8b6b7de22cf08845f8fc02961c9677afb9faf0ddc4efe04ab4073a822a60518b done
#6 exporting config sha256:94013fda37e404bc26b7b15281138cd60afbfea672cae2d2187acc0a7c3a0d7e done
#6 exporting attestation manifest sha256:127fcf7ecd51a985d22f5a33d5c441d8fa1e246f88dc6e990c01197b46a61427 done
#6 exporting manifest list sha256:9a272acf49ab376fa9739f66453979487ea8ab259bc43e56f6553e2da7b20835 done
#6 naming to docker.io/library/mosaic-session:0.0.12 done
#6 unpacking to docker.io/library/mosaic-session:0.0.12
#6 unpacking to docker.io/library/mosaic-session:0.0.12 5.2s done
#6 DONE 24.8s
1 warning found (use docker --debug to expand):
 - InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 299B done
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [1/4] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
#4 DONE 0.0s
#5 [internal] load build context
#5 transferring context: 517B 0.0s done
#5 DONE 0.0s
#6 [2/4] WORKDIR /opt/mosaic
#6 CACHED
#7 [3/4] COPY packages/egress/ packages/egress/
#7 DONE 0.0s
#8 [4/4] COPY contracts/egress.json contracts/egress.json
#8 DONE 0.0s
#9 exporting to image
#9 exporting layers 0.0s done
#9 exporting manifest sha256:db3e8991a9330374257298a4a5d4ff36779e8c8d0b00c8b8ead47fc82e32fada done
#9 exporting config sha256:e7d66f4f785e49a6529b3ed4b708e3b985f4e737d03a66a2c861666288d435d9 done
#9 exporting attestation manifest sha256:8d2362ebe73a0b71b175ea4d9571ab841c6f5b0913e8fa265d1f4544307987d2 done
#9 exporting manifest list sha256:36d0bbeb3abc92fc099d95e1fac416df5cc29edf147fcfc4d8743cbe1d60e592 done
#9 naming to docker.io/library/mosaic-egress:0.0.12 done
#9 unpacking to docker.io/library/mosaic-egress:0.0.12 0.0s done
#9 DONE 0.1s
internal network: listed TLS reached, unlisted host/port refused, external DNS refused
Vikunja client public GET 200; notifier REST dummy-token GET 401
test-install: PASS (provider-host measurement remains Mos gate)
EXIT=0
-363
View File
@@ -1,363 +0,0 @@
# Row 157 / I2 / #1652 — Bentina build record
## 2026-10-24 — recovery and pre-build hold
Authority: Sage sage-3906 under Q246 / mos-1566. Seat thread:
`d1ae468e-51ba-40c4-9513-b39ebbb40450`. Branch `bentina/row157`,
base `ec86f2c84` (required registration commit), initially clean.
Read root AGENTS.md, agents/bentina/README.md, the I2 brief and its shared
rules, and the approved containerized-install design. CURRENT.md is a
historical narrative; queue and Sage's assignment select this row. Goal
order recovered from 2026-09-27_goals-review.md. Boyd first review, then
Darkwing install-path review, per Sage's newer assignment.
### Queue boundary
The initial worktree `scripts/mosaic queue next bentina` refused because
queue commands require the canonical checkout. Retried the same read using
`/mnt/storage/src/mosaic-stack/scripts/mosaic` in that checkout (the documented
queue location, without changing its configuration):
```
start row 157: I2 install images, the install config key, the endpoint helper and the egress proxy; brief docs/plans/2026-10-18_containerized-install-rows.md § I2 install images, the install config key, the endpoint helper and the egress proxy; brief differs from pinned blob; ask the lead to re-pin
```
No claimant move until Sage resolves the stale brief pin. No other queue
write authorized. Sent Sage bentina-0001 with this refusal and the outbound
path findings below. Implementation has not started.
### Re-read of the I2 brief's cited files at ec86f2c84
| Cited file / old site | Current observation |
|---|---|
| scripts/hello.sh:24 | Calls processing-control/src/container-cli.mjs:25; Docker moved behind processing control. |
| scripts/build.sh:13 | Still ambient `docker compose build`:13. |
| scripts/release.sh:39,122 | Still ambient image inspect:39 and compose build:122. |
| scripts/agent.sh:208 | Now execs processing-control/src/container-cli.mjs --tty:209. |
| scripts/verify.sh:36,39,45 | Ambient image inspect:37 and compose build:40; request now uses processing-control container CLI:46. |
| scripts/land-work.sh:87-88 | Ambient network inspect/remove unchanged at 87-88. |
| scripts/test-release.sh:58 | Docker probes now at 78,129; cleanup network rm:25; network checks:131,133. |
| scripts/test-task.sh:139,395,442,517 | Docker probes now at 166,422,470,513,557,579; cleanup network rm:21 and network checks:581,583. |
| scripts/mosaic-task.mjs:396 | Now calls runContainer:402, imported from processing control, rather than directly spawning Docker. |
| packages/cli/src/git-socket.mjs:243 | dropNetwork now spawns ambient docker network rm:266; I6 retains ownership. |
| scripts/mosaic-config.mjs:109-160 / :115 | Loader expanded for instances, Console, federation and compaction settings. validate starts:293; allowed-key expression:304 retains a prefix used by stack-release-fixture.mjs. No install key yet. |
The design says to read via scripts/load_config; this is a function, not a
file. Actual entry is scripts/common.sh:16 `load_config`, invoking
mosaic-config.mjs env, then exporting four existing variables. No install
mode/endpoint exports yet. No modification made.
Design sections 3/4/7 re-read: exact base digest
`node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20`;
three release-tagged images; optional install with local default; exact
rootless SecurityOptions array element check; baked agents/services CONNECT
lists; JSON decision log. Spike proxy source re-read: one-chunk request
parsing, no slow-client bound, environment allowlist; it needs the reviewed
implementation required by I2, not a direct copy. package.json pins pi
0.85.1; spike Dockerfile.clis pins Claude Code 2.1.296 (current session-code
pin still needs checking before implementation).
### Outbound stop condition (brief What ships; design section 7)
The brief explicitly says: “If one doesn't, stop and tell Sage before
adding a dependency.” Not all broker outbound paths use fetch at this base:
- packages/bus/src/gitea-grant.mjs:11 imports node:https request;
send():91-140 calls httpsRequest:115 with `agent: false`, auth header,
optional CA, timeout and bounded response. It is not the measured fetch
path. runtime.mjs imports and wires the grant executor.
- packages/bus/src/web.mjs delegates to packages/discord/src/web.mjs.
That implementation imports node:https/node:http:23-24, resolves public
addresses and pins the connection to a vetted address (:161 onward).
Changing it to fetch without preserving that fence would be a boundary
change. Its arbitrary public destinations also need reconciliation with
the design's fixed services host list; do not widen the list speculatively.
- packages/tasks/src/vikunja.mjs:30,41 uses injected/default global fetch,
redirect:error, and processing cancellation/deadline.
- packages/cli/src/notifier-process.mjs:41-46 supplies global fetch to
openDirect. packages/discord/src/notify.mjs:17 builds REST only; it
explicitly does not run the gateway. packages/discord/src/rest.mjs:45
calls injected fetch. The separate Discord connector gateway uses native
WebSocket (gateway.mjs:193); do not claim the notifier needs that gateway.
- No NODE_USE_ENV_PROXY or HTTPS_PROXY setup found in host.mjs,
notifier-process.mjs or bus/process.mjs. I6 owns container startup;
I2's eventual image/process verification must demonstrate the actual path
with dummy credentials, after the transport ruling and a CLEAR slot.
Recommendation sent to Sage: re-pin the approved brief and rule on the
bounded handling/ownership of non-fetch paths before implementation. No new
dependency, host-list expansion, or replacement of DNS pinning authorized.
### Environment and evidence limits
`/run/user/1000/docker.sock` exists as a Unix socket owned by jwoltje.
Existence is not a rootless attestation. No Docker command or endpoint probe
has been run. All test execution requires Sage CLEAR; none requested yet
because there is no implementation candidate. No test, image build,
credential read, live config/data access, daemon/volume/unit change, commit
or push. Once unblocked, implement within the owned file list, add required
acceptance cases, then request a concrete CLEAR with paths and suites;
use fresh /mnt/storage/scratch/tmp/XXXXXX TMPDIR and timeout -k 10 for each
scripts/test-*.sh. Old-test changes require Sage authorization before edits.
Provider-host measurement ships with expected output for Mos; no real token
will be used here.
## sage-3907 — stop resolved; implementation draft
Sage confirms only I6 changed since the brief pin, and will re-pin row 157.
I2 must not change non-fetch security transports or add a dependency. Record
all gaps, retain each named service host and code path in the contract with
an explicit note, and build the rest. Sage owns the transport ruling before
broker rollout in later rows. This supersedes the pre-build transport hold;
the queue claim still awaits a current pin. No queue move attempted.
Additional non-fetch broker paths found:
- runtime.mjs:511 calls seat/src/working.mjs:461 publishRemote, which runs
git ls-remote/push at :479/:490 with credential helper input on fd 3.
checkout.mjs:80 gitEnv emits a fixed environment without HTTPS_PROXY.
This is Git HTTPS, not fetch; core's no-git design also prevents it from
working today. The Gitea host entry retains this caveat.
- runtime.mjs:292 wires hostGrant; host-grant.mjs:174 spawns ssh-keyscan
to read public host keys at the registered address/port. It uses fixed
PATH-only env, not fetch or HTTP CONNECT. runtime.mjs:435 wires
hostExecutor; host-run.mjs:30-45 sshArgs explicitly sets
ProxyCommand=none and ProxyJump=none, then ssh to run authorized commands
using a pinned host key and private agent. Neither routes through an env
HTTP proxy. Hosts/ports are per registry and may be SSH, so no fabricated
:443 allowlist entry or network expansion was added.
- Search-source plug-ins delegate to discord/src/web.mjs. searxng-url
sends search GETs to the operator's configured host; searxng-bundled points
at 127.0.0.1:8888. Both are non-fetch and depend on the existing DNS checks.
No fixed host can be truthfully named for arbitrary web.fetch or operator
search; no wildcard entry was added. These are documented gaps, not
claims of proxy coverage.
- Separate Discord connector gateway uses native WebSocket; retained
gateway.discord.gg:443 with a NOT PROXIED note, as Sage directs. Notifier
is REST-only; no gateway test or live connector run added.
Choices: dependency-free net proxy; exact list lookup; 8192-byte/5s total
header bounds, 10s upstream connect/120s tunnel idle bounds. JSON allowed
means the contract permitted the destination, not that upstream succeeded.
Images use exact I1 base, core has NODE_USE_ENV_PROXY=1, session globally
installs Claude 2.1.296 and package.json pi pin. Source context includes only
tracked runtime paths plus explicit new I2 runtime paths, never agent work
or host node_modules/.git. Tags derive from RELEASE. Core receives no git.
The zai host candidate api.z.ai is confirmed by the pinned dependency's
providers/zai.js:9 in canonical node_modules; real-token measurement remains
Mos's gate. OpenAI candidate is from S6 and is only measured when configured.
No live model-table config was read.
New tests cover config exit/immutability; helper rootful, null/nonzero,
substring, wrong type/JSON, exact rootless and hostile DOCKER_HOST; proxy
fragmentation/pipelined data, deny/port/malformed/slow/oversize and JSON logs.
test-install additionally builds, inspects pins and exercises an internal
network plus actual Vikunja/public GET and notifier REST/dummy GET with
proxy log evidence. The generic S5 fetch test is not repeated.
Old-suite change is prepared as test-config.patch (adds new config cases
before the existing summary). It has NOT been applied: Sage's old-test
change rule requires approval first. No old tests changed. No tests run.
## sage-3910 — first CLEAR
Authorized four serialized runs exactly as requested, plus applying the
addition-only test-config.patch. No broader suites; Sage supplies the
landing gate. Return slot on run 4 completion or an actual stop.
Additional paths retained under Sage's explicit ruling:
- scripts/install-tests/config.test.mjs: table of install schema/exit/byte
preservation cases, reused by test-config.sh and test-install.sh.
- scripts/install-tests/helper.test.mjs: fake Docker executable to assert
exact argv and rootless refusal before the requested call, without a daemon.
- scripts/install-tests/network-probe.mjs: actual internal-network tunnel,
TLS, unlisted host/port and external DNS refusal in a core image.
- scripts/install-tests/services-probe.mjs: actual Vikunja and notifier REST
client functions, public GET plus invalid dummy token, with proxy log proof.
All are new files; no old assertion changes.
## First CLEAR result and sage-3916 correction
Run 1: exit 0, config/helper 31/31, zero fail/skip/cancel,
01-install-unit.log. Run 2: exit 0, proxy 11/11, zero fail/skip/cancel,
02-egress.log. Run 3: exit 1, config selftest 55 pass / 1 fail; new install
cases 20/20, 03-config.log. Each log records UTC/load/CLEAR/fresh scratch TMPDIR.
Run 4 NOT RUN: no Docker command, probe or build. Slot returned to Sage in
bentina-0004 before any rerun.
Old red: config without console loads with unchanged output
(test-config.sh:266; log:53). Cause: this implementation unconditionally
added install:{mode:local} to resolved JSON. Sage-3916 approves the source-only
fix: omit output key when input omits it (optional-key convention), default
env export to local. Applied this fix to mosaic-config.mjs and adjusted only
the new absent-key case. No old assertion changed. No verification after fix:
Poe has the current slot, Bentina runs next only on a new CLEAR. Run order
will be 1, 3, 4; no reason to rerun unchanged proxy unit tests separately.
The approved test-config patch is addition-only: git diff --numstat reports
4 added, 0 removed lines for scripts/test-config.sh. No existing line or
assertion changed. git diff --stat before first run:
```
BUILD-LOG.md | 2 ++
README.md | 9 +++++++++
docs/SESSIONS.md | 2 ++
docs/TOOLS.md | 38 ++++++++++++++++++++++++++++++++++++++
scripts/mosaic-config.mjs | 20 +++++++++++++++++++-
scripts/test-config.sh | 4 ++++
6 files changed, 74 insertions(+), 1 deletion(-)
```
New untracked paths are not included by git diff --stat; see explicit paths
above. Claim bentina-0003 initially refused queue-lock-held; same op retried.
Sage reports re-pin rev 3808 (sage-3913).
## sage-3919 — corrected acceptance green, slot returned
Run 1 exit 0, helper/config 31/31 (04-install-unit-fixed.log); run 3 exit 0,
config selftest 56/56 plus new install 20/20 (05-config-fixed.log); run 4
exit 0 (06-install.log), helper/config 31/31 and proxy 11/11, images built
at RELEASE 0.0.12, rootless endpoint explicitly attested, image version
checks passed, internal-network TLS to listed host reached, unlisted host
and port refused, external DNS refused. Actual Vikunja public GET returned
200 and notifier REST dummy-token GET returned 401; services proxy logs
show both host decisions. No real token or broker started. Test-owned
networks/containers cleaned by suite. Slot returned bentina-0005. No broader
suites: Sage's landing gate supplies them. Claim bentina-0003 acknowledged
rev 3812 briefed -> in-progress. No queue files changed in this worktree.
Post-pass source review found one small correctness defect before handoff:
upstream errors after a successful CONNECT would append a plaintext 502 to
an established TLS tunnel. Corrected to close an established tunnel and
reserve 502 for establishment errors. Added a local reset-after-CONNECT
regression alongside the existing establishment-error test. This creates
a new verification need: request a new CLEAR for egress units and install
rebuild/probe before committing. Existing green logs above predate this fix;
do not claim them for the final proxy bytes.
## sage-3923 — final proxy and install evidence
07-egress-final.log: exit 0, 12/12, zero fail/skip/cancel, including upstream
reset after a CONNECT200 closes the tunnel without a second HTTP response.
08-install-final.log: exit 0, 31/31 config/helper and 12/12 proxy,
zero fail/skip/cancel, three images rebuilt, version checks passed,
internal-network listed TLS200/unlisted host403/unlisted port403/external
DNS refused, Vikunja public GET200 and notifier REST dummy-token GET401
through services proxy; JSON log decisions verified. Explicit endpoint
unix:///run/user/1000/docker.sock passed rootless check. Suite removed only
its named i2 containers/networks. Slot returned to Sage in bentina-0007.
No runtime source change after these final reruns. Mos-only measurement
runner was statically hardened to report a fixed message on malformed
credential JSON, avoiding JSON.parse's error snippet echoing private input.
The runner and provider script have not been executed here: Mos owns the
real-token/provider-model-table check and its output must land on #1652.
No claim of completed provider-host measurement or final host-list approval.
Acceptance accounting:
- Install validator cases and unchanged config bytes: new 20 cases green;
entire existing config suite 56/56 (05-config-fixed.log); absent output
shape preserved; env/helper default local proven by helper's absent test.
- Rootless guard: exact argv/array membership/status/type/refusal fake
cases plus real rootless check in 08-install-final.log. No ambient Docker
context used by new code. Other scripts' migration remains I6.
- Images: exact base digest in Dockerfiles, release 0.0.12 tags, core node
24.21.0/no-git/RELEASE assertion; session Claude2.1.296/pi0.85.1 assertions;
all built through the helper on the explicit rootless endpoint.
- Proxy: allow/deny/port/malformed/slow/fragmented/header-bound/upstream-error
and structured logs verified in final unit suite; actual TLS tunnel and
internal-network denials plus services client fetch paths verified by
install suite. The generic S5 fetch probe was not repeated.
- Non-fetch broker/gateway/SSH/Git/web paths remain documented gaps under
sage-3907, not modified or silently claimed to be covered. All fixed
service hosts retain entries and code-path notes. No dependency added.
- Full suites: not run here per sage-3910; Sage's landing gate supplies
broader coverage. Reviewer and Mos gates still pending.
Static source diff whitespace check passed (excluding captured .log files
and unified .patch context). Raw build logs retain Docker/npm output
including whitespace; no evidence was normalized or rewritten. Old-suite
change remains +4/-0 and every prior assertion byte-identical. No queue
file changed here. No secret read, token test, live state/unit operation,
daemon/volume/restart action, protected-branch push, or integration.
Candidate handoff: base ec86f2c84; commit only this I2 work and receipts on
bentina/row157, push through configured repository helper, then send Sage
and Boyd the tip/base/manifest for first review. Darkwing follows Boyd.
The manifest lists each changed path except its own self-reference.
## Round 2 — Boyd B1/B2 (boyd-0023; Sage sage-3933)
First candidate a14fdc443, manifest b0ed8395, CHANGES on #1652 comment31494.
Read Boyd's exact scratch harnesses and logs under /mnt/storage/scratch/tmp/IyNqMs.
B1 reproduction: normal peer control releases its socket, allowHalfOpen peer
receives408 but getConnections still reports1 after100ms (assertion1 != 0).
Root cause: refusal end sends FIN but clears the only deadline and leaves the
read side open. B2 reproduction: same-PID exec inherits exported checked_pid
and endpoint; fake rootful Docker sees ps on inherited.sock, no info; rc0 vs
expected2. Root cause: source initializes neither cache validity nor endpoint.
These findings agree with source inspection and require no design departure.
No local tests were run while Goldie holds the slot.
Added tests before fixes, adapted from the reviewer with explicit cleanup:
- packages/egress/tests/refusal.test.mjs: real loopback sockets, normal and
half-open controls for every400/403/408/431 refusal; asserts server socket
count reaches0, one denial log, expected response. Removing destruction
after flush should fail half-open cases while normal controls still pass.
- scripts/install-tests/review-cache.test.mjs: same-PID exec with inherited
exported cache, fake rootful/rootless Docker, actual loader/helper; asserts
first call is info at configured.sock, refusal or exact once-per-process
cache behavior, and no cache keys in Docker's child environment. Removing
source-time reset should fail; no real engine, config or credentials used.
No existing test file or assertion edited.
Minimal fixes: proxy uses endAndClose for all refusal responses, including
upstream establishment502: destroy after the small response flushes, plus a
fixed1s drain deadline cleared on close. Established-tunnel reset behavior
stays unchanged. Helper unsets checked_pid/endpoint/mode on every source,
clearing inherited export attributes; ordinary function calls still reuse
only an attestation obtained in the current process. No dependency, contract,
host-list or authority change. Request CLEAR for new regression red/green,
full targeted units and test-install rebuild before commit. Old-test red
still stops and returns slot. Sage's broader landing gate remains separate.
Sage-3935 approves the five-run plan including archived red-first proof;
Goldie holds the slot and Bentina is next. Still no node/docker test run.
The new refusal test also includes normal/half-open502 establishment failures
on a closed loopback port, exercising the shared termination helper on that
path (allowed log stays true for an approved destination whose upstream fails).
This stays within the named new regression file and approved commands.
## Round 2 verified — sage-3936 slot returned
Claimant move bentina-0012 acknowledged rev3825 in-review -> in-progress,
before the correction commit. Five serialized runs, each fresh short scratch
TMPDIR/UTC/load/CLEAR and timeout -k10:
| Run | Log (this directory) | Exit | Evidence |
|---|---|---|---|
| 1 old archived runtime + new tests | 09-r2-archived-red.log | 1 expected | 12 tests:5 normal-peer controls pass;7 intended failures (five half-open400/403/408/431/502 cases, two inherited-cache cases);0skip/cancel |
| 2 fixed regressions | 10-r2-regressions-green.log | 0 | 12/12,0fail/skip/cancel |
| 3 install unit suite | 11-r2-install-units.log | 0 | 33/33,0fail/skip/cancel |
| 4 egress suite | 12-r2-egress-suite.log | 0 | 22/22,0fail/skip/cancel |
| 5 install images/network/services | 13-r2-install.log | 0 | 33+22 unit tests green;three images rebuilt/version pins checked;explicit rootless guard;listed TLS200,unlisted host/port403,external DNS refused;Vikunja publicGET200/notifier REST dummyGET401;JSON decisions verified |
Run1 unpacked git archive of a14fdc443 into its own scratch directory and
overlaid only the two new tests; it did not revert or modify the builder
worktree. Failures were exact behavioral assertions, not infrastructure or
syntax errors. All test sockets cleaned in t.after; the install suite
removed only its i2-prefixed containers/networks. Slot returned to Sage in
bentina-0014. No existing test file/assertion changed for round2. No code
change after verification. Earlier config56/56 evidence remains in05 log;
loader/config unchanged for this review. No broader suites authorized here.
The round2 manifest MANIFEST-r2.sha256 covers the full candidate delta from
original base ec86f2c84, including the unchanged historical round1 manifest;
it excludes only its own self-reference (its hash is supplied in the review
message). Historical round1 manifest still describes a14fdc443 and is not
rewritten to describe later source. New manifest, candidate tip/base and
receipts go to Sage and Boyd, then Darkwing follows Boyd's approve. The
owner's automated review-request credential boundary still belongs to Sage;
no token read or identity substitution is authorized. Mos provider evidence
and Sage's broader landing gate remain required. No protected-branch push,
real-token test, live config/state/unit action or daemon/volume operation.
@@ -1,44 +0,0 @@
58e473e1783a9f64f6b1a696f747925b9a444d85dbe72e48e7a0dcbd62a2d4e5 BUILD-LOG.md
67bd811b3030ba0355af2316d02e8b2931b4b2c5a74c5be904b5311de6ef81dd README.md
6161cc0505bf0be33ba0e7c7f0f07382cca0ec2b5ace65ac54e16c4d1e3424fc agents/bentina/work/queue-157/01-install-unit.log
f13213941dab22955841108d85e62f67ede2ddbc95c25a7b351f30070b035a85 agents/bentina/work/queue-157/02-egress.log
b46c3fb5f252100f3e2de5b97bd0efc0c1fbbef26230252945f7bb309d359f5e agents/bentina/work/queue-157/03-config.log
fc76e5f9b19a31b494ebf4a128ef5f171f1f0712dc3928bebb6556815577015c agents/bentina/work/queue-157/04-install-unit-fixed.log
c9a46d969c5d28814addccd7cafe8e734d15c02a407ee80434d4e3e70a3fa4b7 agents/bentina/work/queue-157/05-config-fixed.log
1d5505a6aba0078e7cf9936fb683e06ac900af12e9e8faf224099a5e2d825fc7 agents/bentina/work/queue-157/06-install.log
c684dfdd8a460a915e15291fc0f92c9ba30f38d3d3dfa5fa190e7835fbcd448f agents/bentina/work/queue-157/07-egress-final.log
5c3efacdbcda5628882045644c32169b7020a6e49d6b89b273aba20ae042898b agents/bentina/work/queue-157/08-install-final.log
d2f7bd37b5ac8cc781fae1308a5b047e0ed0d1b63f8d5e22d56e0efa328c4883 agents/bentina/work/queue-157/09-r2-archived-red.log
4230f9fcc91d1670df0d975e9c5794edb7aa9b21b24c4f415d1594373e769da4 agents/bentina/work/queue-157/10-r2-regressions-green.log
3e5cd8d154b01ede9a9533f4ff40ac29b4e8193131fd63ba04707df51217d0bb agents/bentina/work/queue-157/11-r2-install-units.log
4d23ae4a6d3247a498bd446960c075235f632474fba9e8db374a32aaf4760219 agents/bentina/work/queue-157/12-r2-egress-suite.log
a4652f0eb6dfcc177c24c5d85d7ec08096e9f49304085f817828d7ef451acd64 agents/bentina/work/queue-157/13-r2-install.log
5b595798fa8184b15412276d070a0891fdf5cb629eabb8b7bb5f6b3d2b69b3a5 agents/bentina/work/queue-157/BUILD.md
b0ed8395108ad398f5c9c08fbac8a1b5ca02f579a5d7b709d19d3aaf0860f9ae agents/bentina/work/queue-157/MANIFEST.sha256
21f9783dcfe2cac00a49d0d7930a57134bc59ec19dd7bf2e96950f79fae5b1b1 agents/bentina/work/queue-157/PROVIDER-EXPECTED.md
6518c574291db5816057fbbc438435d0571e088c8f26d1a47ec03ff3047a5efa agents/bentina/work/queue-157/provider-hosts.sh
6dbef8d20ede98a9311ba47137b587c07cbc03e921b2a8429652fe362aeb4b85 agents/bentina/work/queue-157/provider-runner.mjs
e7cb4d0c67eadec557a25385b4ea9803970818354a9e8b0780ac4de07cf34911 agents/bentina/work/queue-157/test-config.patch
cb2b251342f0020764b7b757b22567d52380053fc15f0e1c7e665a5ec1838f24 contracts/egress.json
58107cfa274b7551ae163ea77c56d08df530e7f08e06539ddd2999c22252d749 docs/SESSIONS.md
edcaa526bf0a518716d4e87895d49920b8912295688809a25102ce6b9d6ebdc3 docs/TOOLS.md
6bd1dc7c244cfb4134d813e0b149ef1182a810eb99f01a95d548f0885b216437 images/README.md
9c6c3d5736f1ed7eb6dcdb893761b937b7108e29730daf28fe8dd0064df73740 images/core/Dockerfile
0179d84a7b9321832efb1d90c0290c8f2f6de03db3b2b6d8379672aca598c2a0 images/egress/Dockerfile
e246e3e3da1065f3c8cf24cc1f7ae15290f3e4c6508f0c6f17a4889a77f2d591 images/session/Dockerfile
3930764cf5848571507eec3e5b3eda2ebb8aa2d9642f83615b462bf5035e0e75 packages/egress/README.md
2c45bfed1f19f8a42e05552f8a10584feed8d25ccc506252d55a9a3b6c90a21c packages/egress/package.json
257058faee55a429d98955b6253d6b8a0bb058c50446764566586b06a8f76b61 packages/egress/src/cli.mjs
ce3adb0515ca7e9041f36d7a82df355a3fc97e2a96fc6811ea3b9b47e464787b packages/egress/src/proxy.mjs
5c4e778ec9ec5b93ab681d615a2240cd342d09adfe4df1634f3d6abbf16a405b packages/egress/tests/proxy.test.mjs
258e954ad7e441a7dd7d25ca9208691ea167e9cb7f34d7d689ed11005adeb4b4 packages/egress/tests/refusal.test.mjs
86e0152120b04fcfe2a4081b7686f179a139c5370027828c79572033f3cf81c3 scripts/images.sh
3647b06df02b4d9c0a74b62c774ecdff9272e68a84c06cd85861d7bf18655d5a scripts/install-tests/config.test.mjs
21e05bc3e0639427f5a9d4cf166d2991964134845272df76c712ad00ea8033b6 scripts/install-tests/helper.test.mjs
aa5328566d3e5b46fee587657dac146712735dca0aca90d3d0ad84bc20e313e1 scripts/install-tests/network-probe.mjs
45274f6745cf931f3002e6c36dcfc46a96c8f09e01e6243c18659749673752fd scripts/install-tests/review-cache.test.mjs
72ae66696ad2faaf258c832fdfdf680efc8fca7099359e58da1ee01a88541d8a scripts/install-tests/services-probe.mjs
79a8ecfc5ab279111031d5a44906450b732b517fe98511992cafd873ab7a84d3 scripts/lib/docker.sh
7398bb289d2f28ed15a2984bdeee7e30a704890a33b5e2a5c9f7554115aca8f9 scripts/mosaic-config.mjs
dcf1799238141689c9d80adc6680694a609e5fb9deef7b78d6b3f4765163e3fe scripts/test-config.sh
08be25f67d8fc6676218b1014d34a2ce36a939231f58b657972d856ee26169b2 scripts/test-install.sh
@@ -1,36 +0,0 @@
6995aa3bcdf50e2441142cc9b6fc9ec84853e353e7a408bbc877ee7e188e73c4 BUILD-LOG.md
67bd811b3030ba0355af2316d02e8b2931b4b2c5a74c5be904b5311de6ef81dd README.md
6161cc0505bf0be33ba0e7c7f0f07382cca0ec2b5ace65ac54e16c4d1e3424fc agents/bentina/work/queue-157/01-install-unit.log
f13213941dab22955841108d85e62f67ede2ddbc95c25a7b351f30070b035a85 agents/bentina/work/queue-157/02-egress.log
b46c3fb5f252100f3e2de5b97bd0efc0c1fbbef26230252945f7bb309d359f5e agents/bentina/work/queue-157/03-config.log
fc76e5f9b19a31b494ebf4a128ef5f171f1f0712dc3928bebb6556815577015c agents/bentina/work/queue-157/04-install-unit-fixed.log
c9a46d969c5d28814addccd7cafe8e734d15c02a407ee80434d4e3e70a3fa4b7 agents/bentina/work/queue-157/05-config-fixed.log
1d5505a6aba0078e7cf9936fb683e06ac900af12e9e8faf224099a5e2d825fc7 agents/bentina/work/queue-157/06-install.log
c684dfdd8a460a915e15291fc0f92c9ba30f38d3d3dfa5fa190e7835fbcd448f agents/bentina/work/queue-157/07-egress-final.log
5c3efacdbcda5628882045644c32169b7020a6e49d6b89b273aba20ae042898b agents/bentina/work/queue-157/08-install-final.log
f4755f24d626dc69b8159c6934e4213a907659dbb4d46d29d82efc0405b50152 agents/bentina/work/queue-157/BUILD.md
21f9783dcfe2cac00a49d0d7930a57134bc59ec19dd7bf2e96950f79fae5b1b1 agents/bentina/work/queue-157/PROVIDER-EXPECTED.md
6518c574291db5816057fbbc438435d0571e088c8f26d1a47ec03ff3047a5efa agents/bentina/work/queue-157/provider-hosts.sh
6dbef8d20ede98a9311ba47137b587c07cbc03e921b2a8429652fe362aeb4b85 agents/bentina/work/queue-157/provider-runner.mjs
e7cb4d0c67eadec557a25385b4ea9803970818354a9e8b0780ac4de07cf34911 agents/bentina/work/queue-157/test-config.patch
cb2b251342f0020764b7b757b22567d52380053fc15f0e1c7e665a5ec1838f24 contracts/egress.json
d6239336e48b6a78b80680c88e83ba4b67f8cbed99bf2ee3a697b1b0611e54e9 docs/SESSIONS.md
edcaa526bf0a518716d4e87895d49920b8912295688809a25102ce6b9d6ebdc3 docs/TOOLS.md
6bd1dc7c244cfb4134d813e0b149ef1182a810eb99f01a95d548f0885b216437 images/README.md
9c6c3d5736f1ed7eb6dcdb893761b937b7108e29730daf28fe8dd0064df73740 images/core/Dockerfile
0179d84a7b9321832efb1d90c0290c8f2f6de03db3b2b6d8379672aca598c2a0 images/egress/Dockerfile
e246e3e3da1065f3c8cf24cc1f7ae15290f3e4c6508f0c6f17a4889a77f2d591 images/session/Dockerfile
3930764cf5848571507eec3e5b3eda2ebb8aa2d9642f83615b462bf5035e0e75 packages/egress/README.md
2c45bfed1f19f8a42e05552f8a10584feed8d25ccc506252d55a9a3b6c90a21c packages/egress/package.json
257058faee55a429d98955b6253d6b8a0bb058c50446764566586b06a8f76b61 packages/egress/src/cli.mjs
2ec533e8a2c9ace2d2dc17ac9efe75a9930ae50e93f6700e3de8aa0475b42295 packages/egress/src/proxy.mjs
5c4e778ec9ec5b93ab681d615a2240cd342d09adfe4df1634f3d6abbf16a405b packages/egress/tests/proxy.test.mjs
86e0152120b04fcfe2a4081b7686f179a139c5370027828c79572033f3cf81c3 scripts/images.sh
3647b06df02b4d9c0a74b62c774ecdff9272e68a84c06cd85861d7bf18655d5a scripts/install-tests/config.test.mjs
21e05bc3e0639427f5a9d4cf166d2991964134845272df76c712ad00ea8033b6 scripts/install-tests/helper.test.mjs
aa5328566d3e5b46fee587657dac146712735dca0aca90d3d0ad84bc20e313e1 scripts/install-tests/network-probe.mjs
72ae66696ad2faaf258c832fdfdf680efc8fca7099359e58da1ee01a88541d8a scripts/install-tests/services-probe.mjs
e524a285dcebe2a6350e7690650c2796e54661225ce00a67b4559e519f9e3fea scripts/lib/docker.sh
7398bb289d2f28ed15a2984bdeee7e30a704890a33b5e2a5c9f7554115aca8f9 scripts/mosaic-config.mjs
dcf1799238141689c9d80adc6680694a609e5fb9deef7b78d6b3f4765163e3fe scripts/test-config.sh
08be25f67d8fc6676218b1014d34a2ce36a939231f58b657972d856ee26169b2 scripts/test-install.sh
@@ -1,37 +0,0 @@
# Mos provider-host gate (not run by Bentina)
Run only after candidate images build and Mos authorizes the real-token calls.
Use a scratch config with install.mode containerized and the rootless endpoint;
do not change the live config. Provide one JSON object on stdin with
`claudeOAuth` and `zai`, plus optional `anthropic` / `openai` keys if those
providers are in the actual model table. The script opens no host token file.
Never paste the input or raw CLI output on the issue. It prints success flags
and the proxy's host decisions only. Wrap in `timeout -k 10 600`.
From the checkout:
```
Mos-private-json-producer | MOSAIC_CONFIG=/path/to/scratch-config.json agents/bentina/work/queue-157/provider-hosts.sh
```
Expected required output (with just Claude Code and pi/zai):
```
{"case":"claude","ok":true}
{"case":"pi-zai","ok":true}
{"hosts":["api.anthropic.com:443","api.z.ai:443"],"denied":[]}
```
Optional pi/anthropic adds `{"case":"pi-anthropic","ok":true}` and no
host. Optional pi/openai adds `{"case":"pi-openai","ok":true}` and
`api.openai.com:443` to sorted hosts. All cases must return ok true, exit 0,
and denied empty. Model names are explicit candidates; Mos must reconcile
them to the actual model table without broadening a host list on failure.
A new host, refusal, failure, or different model requirement returns to Sage;
there is no wildcard or automatic discovery/update. The baked agents list
remains provisional until Mos posts matching output on #1652.
Each resource name begins i2-provider; cleanup removes only this run's two
networks and two containers. No volumes, daemon or restart operations. The
measurement uses container root as the design requires; I5 still owns the
full root filesystem re-measurement and final session limits.
@@ -1,36 +0,0 @@
#!/usr/bin/env bash
# Mos runs this explicitly with a caller-supplied scratch MOSAIC_CONFIG and
# credential JSON on stdin. This script reads no host credential file.
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
cd "$REPO"
source scripts/lib/docker.sh
mosaic_docker version >/dev/null
release="$(tr -d '[:space:]' < RELEASE)"
prefix="i2-provider-$(date -u +%s)-$$"
nets=("$prefix-internal" "$prefix-egress")
cleanup(){
rc=$?; trap - EXIT
mosaic_docker container rm -f "$prefix-session" "$prefix-proxy" >/dev/null 2>&1 || true
for net in "${nets[@]}"; do mosaic_docker network rm "$net" >/dev/null 2>&1 || true; done
exit "$rc"
}
trap cleanup EXIT
mosaic_docker network create --internal "${nets[0]}" >/dev/null
mosaic_docker network create "${nets[1]}" >/dev/null
mosaic_docker create --name "$prefix-proxy" --network "${nets[1]}" --read-only --cap-drop ALL --security-opt no-new-privileges --pids-limit 64 --memory 128m --memory-swap 128m --cpus 0.5 "mosaic-egress:$release" agents >/dev/null
mosaic_docker network connect --alias proxy "${nets[0]}" "$prefix-proxy"
mosaic_docker start "$prefix-proxy" >/dev/null
ready=0
for attempt in {1..50}; do
if mosaic_docker exec "$prefix-proxy" node -e 'const s=require("node:net").connect(3128,"127.0.0.1");s.on("connect",()=>{s.destroy();process.exit(0)});s.on("error",()=>process.exit(1))'; then ready=1; break; fi
sleep 0.1
done
[ "$ready" = 1 ] || { echo proxy-not-ready >&2; exit 1; }
rc=0
# Token travels only in stdin and the CLI child's env; never an engine env or log.
mosaic_docker run --rm --init -i --name "$prefix-session" --network "${nets[0]}" --log-driver none --read-only --tmpfs /root:rw,nosuid --tmpfs /tmp:rw,noexec,nosuid --cap-drop ALL --security-opt no-new-privileges --pids-limit 512 --memory 4g --memory-swap 4g --cpus 2 \
--mount "type=bind,src=$REPO/agents/bentina/work/queue-157/provider-runner.mjs,dst=/opt/mosaic/provider-runner.mjs,readonly" \
"mosaic-session:$release" node provider-runner.mjs || rc=$?
mosaic_docker logs "$prefix-proxy" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const rows=s.trim().split("\n").filter(Boolean).map(JSON.parse);const deny=rows.filter(r=>r.host&&!r.allowed);console.log(JSON.stringify({hosts:[...new Set(rows.filter(r=>r.allowed).map(r=>r.host+":"+r.port))].sort(),denied:[...new Set(deny.map(r=>r.host+":"+r.port))].sort()}));if(deny.length)process.exitCode=1})' || rc=$?
exit "$rc"
@@ -1,17 +0,0 @@
// Mos-only real-token measurement. Reads one JSON object on stdin. Never logs CLI output.
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const exec = promisify(execFile);
let input='';for await(const chunk of process.stdin){input+=chunk;if(input.length>65536)throw new Error('measurement-input-too-large');}
let keys;try{keys=JSON.parse(input);}catch{input='';process.stderr.write('measurement-input-invalid\n');process.exit(2);}input='';
if(!keys.claudeOAuth||!keys.zai)throw new Error('measurement-needs-claudeOAuth-and-zai');
const prompt='Reply with exactly I2_PROVIDER_OK and no other text.';
const env={PATH:process.env.PATH,HOME:'/root',HTTPS_PROXY:'http://proxy:3128',HTTP_PROXY:'http://proxy:3128',NODE_USE_ENV_PROXY:'1',CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:'1'};
const pi=['--no-session','--no-tools','--no-approve','--no-extensions','--no-skills','--no-prompt-templates','--no-themes','--no-context-files'];
const cases=[['claude','claude',['-p',prompt,'--tools',''],{CLAUDE_CODE_OAUTH_TOKEN:keys.claudeOAuth}],['pi-zai','pi',[...pi,'--provider','zai','--model','glm-5.3-flash','-p',prompt],{ZAI_API_KEY:keys.zai}]];
if(keys.anthropic)cases.push(['pi-anthropic','pi',[...pi,'--provider','anthropic','--model','claude-sonnet-5-5','-p',prompt],{ANTHROPIC_API_KEY:keys.anthropic}]);
if(keys.openai)cases.push(['pi-openai','pi',[...pi,'--provider','openai','--model','gpt-5.4','-p',prompt],{OPENAI_API_KEY:keys.openai}]);
for(const [name,bin,args,secrets] of cases){
let ok=false;try{const {stdout}=await exec(bin,args,{env:{...env,...secrets},timeout:120000,killSignal:'SIGKILL',maxBuffer:1024*1024});ok=stdout.includes('I2_PROVIDER_OK');}catch{}
console.log(JSON.stringify({case:name,ok}));if(!ok)process.exitCode=1;
}
@@ -1,12 +0,0 @@
--- a/scripts/test-config.sh
+++ b/scripts/test-config.sh
@@ -291,5 +291,9 @@
fi
echo
+# I2: each install shape asserts its exit code and config-byte preservation.
+node --test scripts/install-tests/config.test.mjs
+check "install configuration cases" $?
+
echo "selftest: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]
-8
View File
@@ -1,8 +0,0 @@
# Boyd
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 298 (the chief-of-staff grant scope, handed over from Megavolt).
No launcher in this directory. The seat runs as a T3 thread (thread 48a3ae38) and works in its own worktree. It has no
credentials of its own; it pushes only its own `boyd/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-272
View File
@@ -1,272 +0,0 @@
# Row 298 (#1804, Q228): chief-of-staff grant scope
Owner Boyd (48a3ae38), reviewer Dewey. Branch `boyd/row298`, base `45dca69f6`.
Assignment: mos-1496/Q245. Handoff: Megavolt megavolt-0203, local `7a141269c`;
cherry-picked as `4de3cec27` (design) and `7cea739de` (UNTESTED code).
Brief: `docs/plans/2026-10-23_mvp-readiness-rows.md`, chief-of-staff grant scope.
Megavolt's original design remains in `agents/megavolt/work/queue-298/BUILD.md`.
## Authority and accepted design
Sage sage-3594, restated in sage-3612: launch.grantScope, lenient read with empty
fallback, strict human-only write. No general organization setter may write it.
Per-method agents/projects or agents/hosts/commands; host-enroll never in scope.
Host command arrays match whole tokens and exact lengths. `grants.scope.set`
from the human CLI is Approve; no Console page or chief-raised scope change.
Mos mos-1496/1502 requires the same human-shell guard as `mosaic access`.
The broker closes an in-scope request via scope and writes grant.scope.used;
missing evidence must refuse execution. Narrowing before spend fails
scope-changed. Empty scope ships; only Jason sets the live value (Q188).
No credential files read, no live grants, no deployment, no queue writes. Push
only boyd/*, additive commits, no amend/force-push or attribution trailers.
Sage owns queue, shared test slot and landing. No Node/tests before CLEAR.
## Implementation plan and ledger
Using writing-plans and executing-plans inline; the approved brief and D1-D6
are the spec. This packet is the requested plan/tracking location.
- [x] Read brief, roster, implementation and handoff; register session.
- [x] Bring Megavolt's two commits onto Boyd's branch without changing history.
- [x] Add independent CLI tests in `packages/cli/tests/grants-scope.test.mjs`:
scope read/set requests; agent-marker rejection before stdin/transport;
--tester rejection; missing revision, malformed/empty/oversize input.
- [x] Strengthen new business/bus grant-scope tests for exact commands,
scope evidence binding, host-enroll exclusion and spent scope-changed.
- [ ] On Sage CLEAR, observe targeted failures before fixes; fix inherited
implementation using the existing human proof and grant executors.
- [ ] Update bus/CLI docs, append BUILD-LOG before/after evidence, manifest.
- [ ] On Sage CLEAR run touched suites; record exact results and mutants.
- [ ] Commit UNTESTED until verified; push boyd/row298 only when slot permits;
report to Sage and Dewey, cc Mos for pushes/blockers. Dewey reviews manifest.
Pre-flight interfaces: business checkGrantScope/grantScopeOf/grantInScope feed
the instance writer and broker. Runtime supplies a fresh scope from the org
file and routes the human setter to the same organization writer. CLI callFor
supplies refuseInsideAgent and humanTransport. The grant executors consume the
broker's immutable close/evidence. Tests use scratch files/fake Gitea only.
Review focus: exact argv length, stale scope before spend, forged/missing scope
evidence, malformed fallback, and CLI agent ancestry. Tests must cover each.
Static findings before any Boyd Node/test run:
- Handoff introduced commandPrefixes beyond the accepted D2 shape, allowing
trailing args. Remove it; only exact command arrays are authorized.
- scope.used carries a digest without validating it, and no org revision.
Bind evidence to the canonical scope snapshot and grant/close sequence;
preserve historical grants when scope is later changed, recheck at spend.
- Gitea recheck throws inside the spend transaction and rolls back spend on
scope-changed. Preserve spend and log failure for that case (D6).
- The optional giteaStart?. assertion always passes when the API is absent;
use the real runGiteaGrant entry point. Fake Gitea tokens() does exist.
## Existing test changes (mos-1272)
Approved by Mos mos-1510 via Sage sage-3624; edited exactly as approved:
| File | Actual numstat | Reason |
| --- | --- | --- |
| packages/bus/tests/project-runtime.test.mjs | +1 / -1 | Vocabulary import pin adds grantScopeOf and grantInScope. |
Other test work is confined to row 298's new files. No pre-existing CLI tests
will change. Record final numstat here before review.
## Human proof and organization ownership
CLI: `packages/cli/src/cli.mjs` callFor runs refuseInsideAgent before constructing
humanTransport, as `accessCommand` does. The setter takes no --tester flag.
`packages/bus/src/human.mjs` verifyHuman checks the nonce, CLI path and every
ancestor's markers/argv; `bindHuman` and #human in broker.mjs require the
business human, via cli, outsideAgent true. Tests will pin this chain's caller
refusal. The scope receipt records by/via/outsideAgent and old/new sha256.
Org file: `packages/business/src/organization.mjs` regular() requires a regular,
non-symlink file owned by the process uid and not writable by group/other;
write() uses revision checks, locked durable receipts and atomic rename.
The OS owner uid can edit it directly; the agent sandbox does not mount the
user's config (`packages/seat/src/sandbox.mjs`). This is the existing cooperative
human/process boundary, not a hostile same-uid isolation claim.
Chief enrollment: broker instances.set calls #human and #mayHuman before
add/change; credential issue/delete use #requestCredential's human gate.
launch.by is an organization-file setting, excluded from general setters;
only that chief may auto-approve within scope. Additional chief instances
remain on the per-grant human approval path.
## Validation
UNTESTED. No Boyd Node/test run yet; waiting for Sage CLEAR.
M1: bypass in-scope check. M2: bypass invalid-value fallback. M3: remove human
setter guard. M4: drop grant.scope.used. All need named failing tests on gate.
## Session correction
The initial SESSIONS entry labels startup 19:13Z; startup was approximately
19:08Z. Message header timestamps in boyd-0001 through 0004 were estimates;
use T3's receipt timestamps as authoritative. Subsequent reports use date -u.
## Departures from Megavolt 7a141269c (Sage sage-3624)
1. Removed commandPrefixes; host scope commands match exact token arrays only.
2. grantScope callback now supplies one organization read `{value,revision}`.
grant.scope.used includes that revision, canonical scope snapshot and digest;
#approvedGrant validates the digest, shape, method, agent and close sequence.
It uses the immutable snapshot so later narrowing doesn't revoke issued grants.
3. Gitea scope-changed is caught inside the spend transaction and logs a terminal
failure without rolling back its spend. Other recheck failures keep their
existing behavior. Host already catches recheck failures after spending.
Additional handoff corrections: replaced optional nonexistent giteaStart with
runGiteaGrant; added successful host executor/revoke and enrollment-exclusion
coverage; added real runtime writer coverage and independent CLI guard tests.
Sage scheduled push at 19:41Z with MOSAIC_GITEA_CREDENTIAL_FILE unset, then T1
lane B around 19:47Z (after explicit CLEAR), four new files whole and serial,
hard stop 20:00Z. Fethry lane A is disjoint. No test was run early. Production
corrections are marked UNTESTED under that slot mandate; baseline regression
checks against the inherited implementation will run only on CLEAR.
## Candidate preparation (before T1)
The three static corrections, CLI/runtime coverage and docs are prepared but
UNTESTED. `git diff --check` is clean. The approved import-pin edit is exactly
1 addition / 1 deletion. M1-M4 are encoded in mutants.py (not run). No Node,
https git, full suite or webui build has run in Boyd's worktree.
Review package: product/test/documentation files in MANIFEST.sha256, excluding
append-only project logs and the preserved predecessor packet. The manifest
identifies exact bytes for Dewey; each later product change regenerates it and
requires a review delta. Existing tests changed: only project-runtime, 1/1.
## Dewey static review dewey-0933
N1: Added a positive control to the malformed-evidence fixture. A valid event
must pass scope authorization and reach decision-mismatch on the deliberately
absent definition binding; invalid events remain not-authorized. No spend.
N2: Additional named mutants cover spend recheck, exact argv length, Gitea spend
rollback, launch.by eligibility and accepting any evidence row (results below).
N3: At spend only grant scope membership is rechecked, not whether the raising
chief is still launch.by. This implements sage-3594 Q5; changing the launcher
later does not by itself invalidate a pending scope-approved request.
N4: The scope shortcut returns the closed decision view to the chief without
sending a separate close message; documented in the bus verb table.
N5: grant.scope is logged after the organization file commit in a separate bus
transaction. A crash between them leaves durable changes.jsonl as the receipt,
as with other organization writers. The setter does not claim cross-store
atomicity.
## T1 lane B results (sage-3642, 2026-10-23 19:23Z–19:29Z)
TMPDIR: `/mnt/storage/scratch/tmp/boyd298-4bzr6fn7`. Each whole file ran serially
as `timeout -k 10 180 node --test --test-concurrency=1 FILE`, TMPDIR set to that
scratch directory. Final restored candidate results:
| Whole file | Pass | Fail | Skip |
| --- | ---: | ---: | ---: |
| business/tests/grant-scope.test.mjs | 7 | 0 | 0 |
| bus/tests/grant-scope.test.mjs | 14 | 0 | 0 |
| bus/tests/grant-scope-runtime.test.mjs | 1 | 0 | 0 |
| cli/tests/grants-scope.test.mjs | 4 | 0 | 0 |
| bus/tests/project-runtime.test.mjs | 3 | 0 | 0 |
29/0/0 total. `final-1.log` through `final-5.log` hold the final output. The
runtime's reload-needs-restart notice is expected: the fixture's boot document
has fewer credential references than its organization document, as in the
existing instances runtime tests. No credential contents are loaded from the
real user config.
Initial run: business 7/0; bus 13/1 due to Boyd's test typo, an out-of-scope
`opts` reference in the missing-scope assertion. Corrected only that new-test
line, then bus 14/0 and the remaining files green. Dewey N1 positive control
was added and the whole bus file passed again; final rerun above followed all
mutants. No production code changed during this test window.
Mutants: all whole files, serial, with timeout -k 10 and the same TMPDIR.
`mutants.py --sage-clear sage-3642 --logs SCRATCH/review-mutants --deadline
2026-10-23T19:38:00+00:00` restores exact original source after each run.
M1/M2/M3/M4 are brief/Sage mutants; M5–M10 cover review focus and dewey-0933.
Each exited 1 with named assertion failures, no loader/syntax kill.
| Mutant | Change | Pass/fail | Named killer |
| --- | --- | --- | --- |
| M1 | skip raise scope check | 11/3 | bus out-of-scope Gitea waits; empty/bad scope waits; host argv exclusion |
| M2 | skip invalid-value fallback | 5/2 | business missing/bad scope fallback; bad file scope fallback |
| M3 | remove #human from scope verb | 13/1 | bus Q3 caller refusals |
| M4 | drop grant.scope.used write | 9/5 | bus Q4 protected evidence assertion; four executor waits also fail |
| M5 | skip scope digest check | 13/1 | bus Q4 bad evidence: decision-mismatch instead of not-authorized |
| M6 | rethrow narrowing, rolling spend back | 13/1 | bus Q5 Gitea spent assertion |
| M7 | same without length check | 6/1 | business Q2 shorter command prefix exclusion |
| M8 | no-op #scopeRecheck | 12/2 | bus Q5 Gitea and host scope-changed outcomes |
| M9 | remove launch.by check | 13/1 | bus Q6 second chief stays open |
| M10 | accept any matched scope evidence row | 13/1 | bus Q4 bad evidence, with positive control |
Mutation log sha256 (under scratch/review-mutants):
```text
55e9973af5fca0088963e2ca11fd81b2fb46dfead813a577c0e0aad2d6269b68 M1.log
abd1b784eff5ae1333a7b47085d3e199cad6d27a00e2584edf0b62417ad4c888 M2.log
1a56085e548d1e244f0f88fe12a45f77c162375880794f9ef88b241ed3a1704d M3.log
9c120f3f7128488edaf8bfd6d41bae712e69e76da4195a0d4dc0e8336b02038f M4.log
1bba88a3752823454acdaa51abde6aad66fdd5488801b4aceda02a3b42c8123d M5.log
a4ce3cacca9f7ea56b8cdfd503c76173a8e4ff1d8a7ec07032c93f0b51aa60d8 M6.log
acecd5afc93d68d96d713f8a9a2dc08351831a0b7360a13b4e30a572ad4167bf M7.log
fce7fa505ccf4f2d4190bd03c209fd19f1cf2770d3ae1246a3bd03697594019e M8.log
a830a7964890dd04a2dcc7a932a1502f1dea06419a1eae6b91a85f7878dc5681 M9.log
38ce3e7f69f6c6ffe10133397465d52538a930a830f76256a8b9280f8df78e4b M10.log
```
Full touched-package suites and every scripts/test-*.sh remain for Sage's full gate; this CLEAR authorized only the five whole files. Candidate remains
marked Untested for the full gate. No queue row has been changed by Boyd.
## Dewey final delta review (dewey-0935, 2026-10-23 19:31:55Z)
Dewey reviewed 574ece13c..bcd93dc08 with local git only, no Node: CLEAN,
nothing blocks. Verified all 13 manifest entries, exact package-file coverage,
project-runtime 1/1, M5-M10 mutation sites, all ten mutation log hashes and
counts, named assertion kills without loader/syntax errors, final five logs
29/0/0, and N1-N5 coverage. No production code delta in this review range.
Formal Gitea APPROVE on #1804 and queue record wait on the row 291 hold release
and Sage's go. Sage's touched-package and every scripts/test-*.sh gate remains
required. Boyd has released lane B and has no running test processes.
## Rebase after row 239 (sage-3794)
Current branch: `boyd/row298-r2`, rebased from published row 298 tip
`2fc27bce6bfac2debe9703782bf308132c26b393` onto
`833ec1cc8d7c11842efb82e57c1a0b256c8d22d7`, which includes row 239 through
`e0cb1db2c`. Original `boyd/row298` remains intact locally and remotely. A new
Boyd branch carries the rebased candidate so no published history is amended
or force-pushed. This section supersedes the original base/branch header for
current candidate work.
Conflicts: only the first Boyd log append in `BUILD-LOG.md` and
`docs/SESSIONS.md` conflicted while replaying 574ece13c. For each, verified the
old commit's log started with the merge base bytes, then retained the new
base's complete log bytes and appended only Boyd's suffix (434 and 281 bytes
respectively). Later Boyd appends replayed automatically. No prior entries
were removed or rewritten. Five commits replayed; the third/fourth range-diff
entries differ only in append-log context; package-only range-diff is all `=`.
The overlapping product files merged automatically: broker.mjs, runtime.mjs
and instance-settings.mjs had no textual conflicts. Inspected their diff
against the new base: only the existing row 298 scope additions remain.
Preserved row 239's injected writer, fire/delete beforeWrite callback, and
commit() passing beforeWrite into writer.write. There is no pre-lock revoke
call in commit(). `packages/business/src/organization.mjs` is byte-identical
to the new base: beforeWrite still runs inside withLock, after revision and
validation checks; post-revoke errors still carry revoked true. Broker's
row-239 error handling and runtime's lock/revoke wiring remain intact.
Static checks only: package patch range-diff all equal, organization writer
unchanged, diff whitespace clean, refreshed 13-file package manifest verifies.
The only pre-existing row-298 test change remains project-runtime.test.mjs
exactly 1/1 under mos-1510. No test files were changed for this rebase.
UNTESTED on the new base: no Node, tests, mutants, builds or queue writes run.
Earlier 29/0 and M1-M10 results apply to the old base only. Await Sage CLEAR
for whole touched-file reruns; Dewey reviews the new candidate.
Row 321 (#1817) review assignment acknowledged; waiting for Sage's request
and Gladstone's pushed docs-only F7/F10 run-sheet candidate.
@@ -1,13 +0,0 @@
612488b705853e75e6f8952e41552526f4d07dd297d7619d608d434ed75121d6 packages/bus/README.md
63fdf6c048f0994af3551510119eeb294c71f8116702772844214930b827cc56 packages/bus/src/broker.mjs
65425ead353017c99e04f1e0bcc3f85c4ba60054da293badaa0360e681e418dc packages/bus/src/runtime.mjs
e7065db949cacfd4679a1031eebf5cde09ed196ce85612e8400ae563f02a0f18 packages/bus/tests/grant-scope-runtime.test.mjs
4030cf584481a1db12d9e6f744015c48fdebd468335d065ead3e90180858d366 packages/bus/tests/grant-scope.test.mjs
bc432d8d430433b9d84c00883584dc53d4b27afb5e93f739fe3ca28e80f01efa packages/bus/tests/project-runtime.test.mjs
84d6dd15d5ca2926d8f7a34548c70c3e7a3c58e692f0b009414f1af35cb096cc packages/business/src/business.mjs
e3b425f9ad2a0d4e6e0b55bd48827e144015662d95ace4163fe6474e5f98da81 packages/business/src/instance-settings.mjs
fed4177325f65a2b9823a40a9c35c01d60c0520e3ff40b4574c3cf2c29cc096b packages/business/src/vocabulary.mjs
b7f96e45b9ffca2a7ebc18dc446f0adcaac72d3900f098c60157cf6cbf1bfc13 packages/business/tests/grant-scope.test.mjs
60fc267b7f8f9e83eeb0d0207c8abdef68d554f32b08d508e98dbf59b442a366 packages/cli/README.md
b586907ca8f351f60dc19ec2683df75d2d70ddb12955d09fb14560c9d3bed3dc packages/cli/src/grants.mjs
fb7c30b31facd887b03ff02dd6734d48a815978481b33bb21ec48604a51c5ff5 packages/cli/tests/grants-scope.test.mjs
-105
View File
@@ -1,105 +0,0 @@
#!/usr/bin/env python3
"""Row 298 mutants. Run only with Sage's explicit CLEAR in Boyd's slot.
Each mutation is applied to this worktree, runs one whole new test file,
then restores the exact original bytes, including after an interruption.
Logs go to a caller-supplied scratch directory, never the repository root.
"""
import argparse
import datetime
import pathlib
import subprocess
ROOT = pathlib.Path(__file__).resolve().parents[4]
BROKER = ROOT / 'packages/bus/src/broker.mjs'
VOCABULARY = ROOT / 'packages/business/src/vocabulary.mjs'
BUS_TEST = 'packages/bus/tests/grant-scope.test.mjs'
BUSINESS_TEST = 'packages/business/tests/grant-scope.test.mjs'
def replace_once(source, old, new):
if source.count(old) != 1:
raise RuntimeError(f'expected exactly one mutant site, found {source.count(old)}')
return source.replace(old, new, 1)
def mutate(name, source):
if name == 'M1':
return replace_once(source,
'if (!raise?.grant || !grantInScope(scope, raise.grant)) return view;',
'if (!raise?.grant) return view;')
if name == 'M2':
return replace_once(source,
'return scope === null ? { scope: GRANT_SCOPE_EMPTY, fallback: true } : { scope, fallback: false };',
'return { scope: scope ?? value, fallback: false };')
if name == 'M3':
return replace_once(source,
' #grantScopeVerb(s, verb, a) {\n this.#human(s);',
' #grantScopeVerb(s, verb, a) {')
if name == 'M4':
old = """ this.#grantEvent(s.business, {
action: 'grant.scope.used', grantDecision: d.id, decisionSeq: seq, method: raise.grant.method,
agent: raise.grant.agent, scope, revision, scopeSha256: sha256(JSON.stringify(scope)),
}, d.id);"""
return replace_once(source, old, ' // MUTANT: scope evidence omitted.')
if name == 'M5':
return replace_once(source,
"evidence.agent === grant.agent && evidence.scopeSha256 === sha256(JSON.stringify(checked.scope)) &&",
"evidence.agent === grant.agent &&")
if name == 'M6':
start = source.index(" checked = this.#store.transaction(() => {\n const consumed = this.#consumeGrant")
end = source.index('\n } catch (e) {', start)
return source[:start] + " checked = this.#store.transaction(() => this.#giteaRecheck(business, decision, this.#consumeGrant(business, decision, 'gitea')));" + source[end:]
if name == 'M7':
return replace_once(source,
'const same = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);',
'const same = (a, b) => a.every((x, i) => x === b[i]);')
if name == 'M8':
return replace_once(source,
" if (this.#closed(decision)?.via === 'scope' && !grantInScope(this.#scopeNow(business).scope, g)) fail('scope-changed');",
" // MUTANT: no spend-time scope recheck.")
if name == 'M9':
return replace_once(source,
' if (s.role !== this.#business(s.business).launch?.by) return view;',
' // MUTANT: any chief instance can use scope.')
if name == 'M10':
start = source.index(' const evidence = JSON.parse(row.body);', source.index(' #scopeUsed('))
end = source.index('\n }', start)
return source[:start] + ' return true;' + source[end:]
raise ValueError(name)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--sage-clear', required=True, help='CLEAR message key; required before any Node run')
parser.add_argument('--logs', type=pathlib.Path, required=True)
parser.add_argument('--mutant', choices=['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'all'], default='all')
parser.add_argument('--deadline', default='2026-10-23T19:38:00+00:00')
args = parser.parse_args()
deadline = datetime.datetime.fromisoformat(args.deadline)
args.logs.mkdir(parents=True, exist_ok=True)
names = ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10'] if args.mutant == 'all' else [args.mutant]
for name in names:
remaining = (deadline - datetime.datetime.now(datetime.timezone.utc)).total_seconds()
if remaining <= 15:
raise RuntimeError('Sage slot deadline reached; no new test run')
path, test = (VOCABULARY, BUSINESS_TEST) if name in ['M2', 'M7'] else (BROKER, BUS_TEST)
original = path.read_bytes()
mutated = mutate(name, original.decode())
log = args.logs / f'{name}.log'
try:
path.write_text(mutated)
with log.open('w') as output:
result = subprocess.run(['timeout', '-k', '10', str(int(min(180, remaining - 13))), 'node', '--test', '--test-concurrency=1', test],
cwd=ROOT, stdout=output, stderr=subprocess.STDOUT,
timeout=min(195, remaining - 1), check=False)
print(f'{name}: exit={result.returncode} log={log}', flush=True)
finally:
path.write_bytes(original)
# Exit 1 needs human inspection of the failing test names; errors are not kills.
if result.returncode == 0:
raise RuntimeError(f'{name} survived')
if __name__ == '__main__':
main()
-8
View File
@@ -1,8 +0,0 @@
# Bubba
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 288 (agent write notices and library revert, handed over from Honker).
No launcher in this directory. The seat runs as a T3 thread (thread 3d6672b5) and works in its own worktree. It has no
credentials of its own; it pushes only its own `bubba/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
+304
View File
@@ -0,0 +1,304 @@
# Row 288 (#1793): C2-02 agent writes, notices, revert and Console saves
Owner: Bubba, Codex gpt-6.1-sol, thread 3d6672b5-5544-4b95-9e09-e79574f1aa14.
Reviewer: Webby. Gate and landing: Sage. Append-only build record.
## 2026-10-23 start and implementation plan
Base 45dca69f6 on bubba/row288. Direct mandate mos-1499 (Q245), confirmed sage-3613.
The canonical `scripts/mosaic queue next bubba` returned `nothing`; Sage owns the owner transition.
The initial worktree invocation refused linked-worktree use; retried in canonical checkout.
Spec: docs/plans/2026-10-20_case2-rows.md, C2-02; agents/gosalyn/work/case2/DESIGN.md ruling after r11.
Handoffs: Honker queue-283 BUILD.md Q4 (missing/non-100644 delete refuses); Morgana
queue-225 BUILD.md A2 at 66f7becf2 (strict revert arguments/result), confirmed by Fethry fethry-0005.
`project` is the business.projects key, never a chat UUID.
Dependencies merged with their commits intact, no amend or force-push:
- 283 origin/honker/row283-build 88ec5e8e4 (new E4 head to follow).
- 285 origin/scrooge/row285-r2 53db6b7ca.
- 286 origin/ludwig/row286-r2 573fa0be0.
- 284: no named remote branch yet. Tool changes come last.
All merge commits are marked Untested. Nothing has run under Node. Sage's latest instruction
sage-3613 reserves **all Node and test runs** for CLEAR; this supersedes the initial targeted-test
permission. Build untested now as instructed, then run RED/GREEN checks/mutants on CLEAR.
Architecture: one host-only plumbing writer under the existing library lock; broker authorizes and
selects credentials, then inserts the fixed notice directly. Human save/revert use editor +
library-human independently of seat lifecycle. No worktree, deferred push, role expansion or UI.
### Tasks and acceptance
- [ ] Plumbing: new library/src/write.mjs, mirror state publication and package exports; new
library/tests/write.test.mjs. One-parent write/delete, byte-exact content, seat identity/trailer,
stale-base, nonregular/missing deletes, retry once on an unrelated remote change, push failure
without local ref movement, human editor and revert reachability/single-path checks.
- [ ] Broker + runtime: library.doc.write/delete, library.file.save, library.revert; strict keys,
creative + library.write + live unheld project grant; direct exact notice; human-only save/revert.
New bus/tests/library-write.test.mjs covers authorization, unchanged refs, A2 contract and socket.
- [ ] Transport: only human library.file.save joins the existing large-save exception. New tests
prove >64 KiB save accepted, agent writes retain 64 KiB bound, malformed/oversize saves refuse.
- [ ] Tools after 284: library_doc_write/delete in harness tools; exact eight/ten-name snapshots
in a new harness test; cli dispatch wiring.
- [ ] Verify on CLEAR: new tests, library/bus/harness suites and all scripts/test-*.sh on Sage's
assigned slot. Mutants remove supporting guard and force push; both must fail. Fixture-token
search over captured output. No gate, landing or queue move claimed before Sage's acceptance.
Pre-flight interfaces: broker consumes library writer {project,path,commit}; revert adds reverted;
writer consumes C2-03b push {pushed} or {raced,tip} and supportingPath(mapping,universe,path,tree).
State publication must add optional lastPush while preserving old state readability. Refusals must
restore fetched remote-tracking refs as well as preserve served. Runtime must bind credential file
references itself; no file, identity, message or credential is supplied by the agent.
Necessary file-scope additions: packages/bus/src/runtime.mjs (actual adapter injection),
packages/library/src/index.mjs (exports), and CLI's existing console adapter if saves require it.
Reported to Sage in bubba-0002. Existing tests will not be edited without Mos's OK via Sage.
Review focus: remote tip races, nonregular entries and folded paths, unsafe human credential before
git, revert after revoked/fired writer, and large-request exception never extending agent writes.
### Existing-test changes
None authored by Bubba yet. Dependency test changes remain their owners' original commits.
## 2026-10-23 first static build (not a gate result)
283 E4 merged intact at 76d651dba. Honker next reports e67c27b32 (test environment only), to merge
before push. Della reports row 284 has not been built/pushed; row224's 23816cf28 is not a 284 head.
No speculative 284 pin. Typed tools and served-blob lookup integration remain pending 284.
Implemented (unrun): bare plumbing writer, one-parent commit and fixed trailer, exact-content blobs,
base comparison, supporting path guard and delete regular-blob check, one remote-race retry,
ref restoration on refusal, immediate served publication/lastPush; human save and reachable one-path
business-trailer revert; broker grants/identity/notice path; runtime adapter wiring; large-save socket
exception. New tests: library/tests/write.test.mjs and bus/tests/library-write.test.mjs. No old test edits.
### Approved scope additions, sage-3628
Every hunk outside the brief's owned list:
- `packages/bus/src/runtime.mjs`: `humanWriteOptions` checks editor before choosing the human slot;
three library adapter entries bind dataRoot, command and the host-only seat store reference. These
are needed to make actual bus calls reach the new writer without accepting agent-supplied files.
- `packages/library/src/index.mjs`: export writeLibraryDoc/saveLibraryFile/revertLibraryCommit so
runtime's existing library module adapter can call them.
- `packages/library/src/sync.mjs`: shared write guard with opaque token compared by identity;
push's race fetch recognizes only that owner and does not clear its guard; ordinary sync and a
forged token still refuse library-sync-busy. Narrow publishLibraryWrite validates ids, reads the
locked state, rebuilds the index via readLibrary, moves served/tracking and writes lastPush. The
raw writeState remains private and is not exported through index.mjs.
Ludwig ludwig-0064 reviewed the intended interface and requested the opaque token (implemented),
narrow publication (implemented), whole library + bus library-credential checks and a token mutant
(planned). His 573fa0be0 remains current and unrun. 288's additions are separate commits, not folded
into 286. Tracking restores only if the whole write refuses, not between the first race and retry.
Known tradeoff, accepted by Sage sage-3628 for MVP: the brief holds the flock through network Git;
readers can hit the existing 5-second library-sync-busy while it runs. Structural supportingPath
preflight is pure and precedes Git; served/fetched listTree checks repeat it with the real tree before
any remote write. A tree-dependent check necessarily reads the tree first (Honker honker-0184 item2).
### Mutants for CLEAR
- Remove the `pathOf` calls for agent operations: the M12 broker/bare-ref tests must fail on a
story/unsupported path while checking remote, tracking and served refs.
- Prefix the push refspec in syncCommand with `+`: write.test moved-target race must fail, because
its command seam preserves production syncCommand's refspec (only remote URL/protocol are fixture).
- Replace `writers.get(mirror) === writeToken` with `true`: write.test forged-token case must fail.
No Node, test, build or mutant has run. `git diff --check` is clean. Counts are not claimed.
### Console dispatch integration
The existing cli delegates its transport wiring to console-bus.mjs, so the save dispatch needs that
file too: one null `librarySave` option and one `--send` assignment to library.file.save. cli.mjs
threads that option into startServer; row C2-11b owns the actual route/page that consumes it. New
webui/tests/library-write-options.test.mjs covers --send and the user transport boundary. 225 owns
libraryRevert dispatch; 288 adds only librarySave to avoid overlapping that ownership. Additional
scope reported to Sage and Fethry. No changes to an existing test.
## 2026-10-23 second static pass and dependency review
First push: c867cada9 on origin/bubba/row288, reported bubba-0005 to Sage/Webby/Mos and
bubba-0006 to Fethry/Ludwig. No PR created and no review/gate requested as complete.
Ludwig ludwig-0065 checked the exact sync diff against 286 without running anything. Token identity,
ordinary-sync exclusion, 286 R1 preservation, guard key and end-of-write tracking restoration fit.
F1: state publication could fail after served moved, leaving old state/new served. Root cause is
publication order without rollback, compared against sync's existing rollback. Added an unrun
filesystem rename-failure regression, then changed the narrow helper: on failure it validates the
state under the same lock, CAS-restoring served if state has not published the new commit. If rename
succeeded but directory fsync failed and the state validates against new served, it keeps that pair.
All failure cases answer outcome-unknown; tracking remains the commit already pushed remotely.
Verification is pending CLEAR, so this is a proposed fix, not a tested resolution.
Added the actual broker-to-bare tests for each M12 path refusal (both agent verbs), unchanged remote
and all mirror refs, exact write/delete notices, and human revert after fixture grant removal/hold.
The bare fixture moved to new library/tests/write-fixture.mjs so broker tests reuse real plumbing.
Added the raw socket proof-nested-save-name case to pin the parsed-verb large-request check.
Missing editor/slot tests now make the mirror metadata invalid first: reaching Git would change the
refusal, so the tests check the pre-Git gate rather than merely count remote commands.
Additional required doc hunk: packages/library/README.md appends the four verb contracts,
lastPush shape, exact notice, lock tradeoff and publication failure behavior. No existing test from
the dependency/base has changed. All new/modified tests in this pass were authored by this row.
`git diff --check` remains clean. No Node/tests/build/mutants run; 284 and CLEAR remain pending.
## 2026-10-23 early review follow-up (Webby E1-E6; Ludwig F2)
Sage sage-3652 approves console-bus.mjs wiring (null option + --send dispatch), confirms editor.user
as the sole human login source and reserves CLEAR for T3/T4 after T2. Ordinary sync()'s separate
post-rename fsync gap stays out of 288 per that ruling; no change to sync()'s existing rollback.
Honker 283 head 46bad7ea3 merged intact: mapping editor.user is optional, and if present uses the
existing plain Gitea-user pattern. The omission at mapping level is valid; human writes refuse it.
- Ludwig F2 / Webby E6: confirmed the previous runtime used the fetch bot's mapping.user wrongly.
Runtime now passes mapping.editor.user to humanCredential, never mapping.user. Missing user gives
library-human-missing before Git. New real-runtime cases pin both save and revert with a separate
fetch-bot login, positive pushes under her-gitea-login, and no command for an absent editor login.
The broker's credential.agent is s.role, not args: added an exact assertion and strict-key refusal
cases for caller-supplied agent and credential.
- E1: replaced the business-id RegExp interpolation with a literal startsWith(business.id + '/')
check and ID_PATTERN on only the instance suffix. New acme.co vs acme-co trailer test.
- E2: new broker→bare case holds the seat in the fetch command. beforePush must refuse
library-write-no-grant with all local refs and the remote unchanged and no notice. New mutant:
remove `options.beforePush?.();` in write.mjs; the E2 test must fail.
- E3: new broker cases for expired rotate_by, fire.stopped without fire.done, missing role.publish
project, grant/identity Gitea-user mismatch, and missing identity name/email, for both agent verbs.
- E4: the exceptional publication failure currently returns outcome-unknown before the broker has
a successful result to put in a notice, so a remote agent commit can exist without a Console
notice. There is no durable notice outbox/retry in this row. The trailer commit remains human
revertible by id after sync; this is an explicit known failure-mode gap for the manifest, not
a claim that every failed publication emits a notice. A new real-push test breaks state decoding
after push admission, pins outcome-unknown, old served and no notice. It also found a local error
propagation gap: errors before publishLibraryWrite's internal try now normalize to outcome-unknown
in the writer once push has succeeded, so the caller is never told an ordinary refused-write code.
- E5: 284 still has no build. Tools/8-or-10 snapshot and served-blob integration remain pending.
librarySave is intentionally only an adapter option until C2-11b supplies its route/page.
All tests above are newly authored by row288 and remain unrun; no changes to baseline/dependency
tests. RED/GREEN and mutants await CLEAR. Proposed fixes are not claimed verified. The new host-only
library write module imports the existing pure ID_PATTERN to match the broker's accepted instance ids.
## 2026-10-23 mos-1520 ruling: post-push notice is required
Correction to the E4 interpretation above: Mos rejected the known-gap treatment. The requirement
stays: a pushed agent commit has the exact notice even if local publication then fails. This is not
an outbox or retry. The writer now calls the broker's trusted synchronous onPushed callback as soon
as push returns pushed:true, before publication. The callback records the exact library-write notice
and action.allowed in the normal Console-store transaction. Publication failure still answers
outcome-unknown. The notice's Revert can refuse library-revert-not-agent until sync makes that commit
reachable from served; README now says that explicitly and removes the rejected gap description.
The row's own real-push corruption test now pins the exact notice body with the pushed id, old
served and outcome-unknown; after restoring readable state it pins Revert's refusal before sync and
success after sync. The successful-write adapter fixture calls onPushed too, matching runtime's
writer contract. No existing baseline/dependency test changed (mos-1520 explicitly authorizes this
row's test correction without mos-1272).
Residual Console-store failure: if notice insertion/routing/its event transaction fails, the broker
calls a separate host log sink with {event:library-notice-store-failed,code:outcome-unknown,business,
instance,op,commit}. It logs no args, path content, credential, raw SQL or exception output. Runtime's
new library.log adapter writes this metadata to stderr. The caller gets outcome-unknown even if
recording action.refused in that same broken store fails. A new real-push test uses temporary SQLite
abort triggers on notice insertion and refusal evidence, checks remote advanced/served unchanged,
no message, the exact external log and outcome-unknown. It does not claim a notice can be stored in
a failed Console store. If the host log sink itself also fails, no durable log can be promised; the
caller still receives outcome-unknown. The runtime log hunk is the required residual-failure wiring.
Mutant added: move `options.onPushed?.({ project: context.project, path: change.path, commit });`
from before `await publishLibraryWrite(...)` to immediately after that await inside the same try.
Successful operations keep their notice, but the outcome-unknown publication path drops it. The
C2-02 E4 real-push corruption test must fail on its exact notice assertion. Run on CLEAR and restore.
No Node/tests/build/mutants run. Static git diff --check clean. Della's first284 head0ed4fbab9 is
explicitly incomplete and not a finished dependency; waiting for her complete-head pin before tools.
Webby webby-0253 (static, cef503cb9) closes E4 per mos-1520; no tests ran. N1: a failed notice insert throws from onPushed before publication, so served stays at the prior commit until sync; the store-failure test pins this safe direction. Only E5 (284 tools/snapshot integration) remains open in this early review.
## 2026-10-23 complete284 merge and final tool integration (unrun)
Della della-0037 supplied complete untested head47db04eba85ac3ceb7e81385603f97bb6f8c1a38,
replacing the incomplete head. Merged intact as c0ca3894c. Its22423816cf28 and2164f3fbc25a
lineage is retained. Three conflicts were in overlapping production wiring only:
- broker.mjs: keep Della's LIBRARY_LOOKUPS object and #requestLibraryLookup byte-for-byte, and
keep288 writer/notice methods; LIBRARY_VERBS contains both sets and request dispatch calls lookup.
- runtime.mjs: keep lookup adapter plus288's metadata log and write adapters.
- library/index.mjs: keep index/query exports,286's push/humanCredential exports and288's writer exports.
No dependency test body was changed in the merge. These are overlapping consumer integrations,
not rewrites of dependency commits. Every prerequisite still needs its own gate (mos-1351).
Added library_doc_write/library_doc_delete after the five lookups, only for a creative definition
with a within-role library.write grant and library enabled. Closed schemas carry exactly
{universe,path,base,content} and {universe,path,base}; base is string-or-null with 40-lowercase-hex
pattern. No project, business, author, credential, commit or message. tools.action is library.write.
New harness/tests/library-write-tools.test.mjs pins per-role eight/ten names, both schema invariants,
no repo/commit/business/accept, exact required/verb/action, nullable base, and missing library/grant
or noncreative exclusion. This satisfies sage-3678 and mos-1523's replacement coverage condition.
Publication's narrow helper now calls indexFor for the new served commit instead of only readLibrary,
so the284 lookup index is rebuilt before publication. New broker integration uses library.get's
served blob as write/delete base, independently checks served:path, reads the new text/blob immediately,
preserves default-canon null, then sees null after delete. No change to284's query behavior.
### Authorized baseline-test migration, own commit (mos-1523 via sage-3677)
File: packages/harness/tests/library-tools.test.mjs. Numstat: +4/-4, exactly.
The noncreative LOOKUPS assertion at :33 is unchanged. Every existing lookup schema assertion stays.
The four old/new lines are:
```diff
- ? ['web_search', 'web_fetch'] : []), ...LOOKUPS];
+ ? ['web_search', 'web_fetch'] : []), ...LOOKUPS, 'library_doc_write', 'library_doc_delete'];
-test('M3: per-role snapshots contain six names, or eight with the C2-08 web grant', t => {
+test('M3: per-role snapshots contain eight names, or ten with the C2-08 web grant', t => {
- for (const [i, tool] of tools.filter(t => t.name.startsWith('library_')).entries()) {
+ for (const [i, tool] of tools.filter(t => LOOKUPS.includes(t.name)).entries()) {
- assert.deepEqual(tools.map(t => t.name), ['send_message', ...LOOKUPS]);
+ assert.deepEqual(tools.map(t => t.name), ['send_message', ...LOOKUPS, 'library_doc_write', 'library_doc_delete']);
```
Additional schema mutant (mos-1523): add `commit: str("caller commit")` to writeBase in tools.mjs,
thereby adding it to either/both write schemas. The new-file schema test must fail both its exact
property set and forbidden-property assertions. Run on CLEAR and restore. No further assertion
change in library-tools.test.mjs is authorized or made.
All code/tests/mutants remain unrun. No Node, build or syntax check before Sage's named CLEAR.
Static diff --check is clean. Scope additions stay listed above, now including indexFor import and
call in the previously approved narrow publication helper. E5 implementation is present; review and
acceptance are pending evidence, not claimed complete.
## 2026-10-23 testing and landing boundary (sage-3682)
Latest complete dependency2845d587a552 (test/docs only over47db04eba) merged intact as f8f79ea2f.
Own tool implementation cd2f50dae and separate mos-1523 migration29455c0c5 are pushed; Webby reads
E5 and Dewey checks the migration. Static manifest-r1 covered21 own paths at f8f79ea2f.
Sage reserves first focused runs/RED-GREEN/mutants for T7, provisionally about22:25Z, unless he
explicitly frees an earlier lane. No Node before his CLEAR. This is a schedule, not CLEAR. Full
library/bus/harness and shell gates still need their own authorized slot; no gate result is claimed.
Before the gate, incorporate landed refactor after283/224/284 land while preserving every
prerequisite. Under the standing add-commits/no-force-push rule, update this published branch by merge
rather than rewriting its pushed history. 288 lands last in the chain; no landing is authorized here.
## 2026-10-23 E5 static closure and N2 wording
Dewey dewey-0948: the mos-1523 migration at29455c0c5 is static CLEAN, exactly+4/-4, with the
noncreative assertion unchanged and new-file schema coverage meeting every condition. Webby
webby-0263: E5 closed on29455c0c5/cd2f50dae (f8f79ea2f;98ca25ab1 only BUILD.md), nothing blocking.
Both ran nothing; Webby did not read the scratch manifest, so no manifest verification is implied.
Webby N2: delete inherited the write base description "or null to create". Delete now overrides only
that description: "The existing file's blob from library_get; a missing file cannot be deleted".
The reviewed nullable-base schema/required keys/verbs remain unchanged. The row's new schema test
pins create wording only on write and existing-file wording without create on delete. No further
assertion change in the baseline library-tools.test.mjs. No Node/tests run; still await Sage CLEAR.
## 2026-10-23 dependency283 follow-up (honker-0195)
Merged origin/honker/row283-build b2a1f4092 intact, fast-forward lineage from46bad7ea3. It changes
supporting.test's helper and Honker's BUILD receipts only; no production source changed. Honker
reports library98/98, business231/231 and14/14 mutants killed on that dependency head. Those are
his dependency receipts, not verification of288's combined branch. No288 Node/tests run; Sage's
CLEAR is still required. The dependency's own gate/landing remains Sage's responsibility.
+170
View File
@@ -0,0 +1,170 @@
# Row 224 — C2-05b broker lookups and typed tools (#1725)
Owner: Della; reviewer: Dewey. Assignment sage-2795; scope extension sage-2804.
Base: `4f3fbc25a` (Researcher C2-05a). Branch: `della/row224`.
Brief: C2-05b in `docs/plans/2026-10-20_case2-rows.md` at `aca16404c`
and `51cc0445c`; design section 3, 5.3 M3 and 7.2; C2-05a BUILD rulings.
## Execution boundary and progress
Code/docs and local Git only while row 253 holds the gate. No Node (including
syntax checks), HTTPS Git, queue claim, test or push until Sage's CLEAR.
Tests are written first; their RED/GREEN runs are deferred, not claimed.
Queue next is deferred too: its executable uses Node. Sage assigned this row
explicitly. After 253 and 216 land, rebase under a new branch name.
- Pre-flight: query.mjs consumes `{repo,commit,mapping}`; broker cannot import
Git/filesystem library code because it is in the sandbox import closure.
Runtime injects the host lookup adapter; existing human file/status/sync stay.
- Pre-flight: resolved has no project list and a library is never a checkout.
Bundle needs an explicit library flag from launcher business configuration.
- M3 reading: exact creative base is five lookups plus send_message; retain
explicitly granted broker web_search/web_fetch for research/companion under
landed C2-08 and sage-2804. No generic spec, task, evidence or decision tools. Residual (mos-1308): those
reads lack broker action checks; their typed-tool absence is the only fence
for creative roles. M1/M2 deny bash; broker-side refusal is outside this row.
Mos confirmed this interpretation in mos-1308; design wording fix belongs to Gosalyn. Other definitions retain their current tools.
- Multi-library businesses use an optional project ID; omit it only with one
configured library. Universe is required for all five tools, including get.
- Missing library uses the brief's `library-not-configured`; argument errors
reuse `invalid-request` and C2-05a query codes. No new action or authority.
## Manifest (draft code, unrun)
- bus/src/broker.mjs: five lookup verbs, strict args, holder/business rechecks.
- bus/src/runtime.mjs: host lookup adapter, stale-before-read sync, served pin.
- harness/src/tools.mjs: five schemas; creative exact-set filtering.
- harness/src/bundle.mjs: library flag forwarded to typedTools.
- cli/src/launcher.mjs: one flag derived from trusted business projects.
- bus/tests/library-lookups.test.mjs: real query/broker/socket/runtime cases.
- harness/tests/library-tools.test.mjs: schemas, role snapshots, bundle parity.
- cli/tests/thread-launch.test.mjs: flag reaches a real launched bundle with no library checkout.
- agents/della/work/queue-224/BUILD.md: manifest, rulings and deferred verification ledger.
## Verification ledger
No test or syntax-check run yet. Acceptance requires full bus/cli/harness suites
and every scripts/test-*.sh on Sage's gate; focused files and affected CLI/
seat import-closure tests precede that only in an authorized CLEAR.
## Coordination
Huey272 reports no runtime/bundle/launcher overlap. Rocko219 changes launcher
THREAD_DEFINITIONS checks only, separate from our buildBundle flag argument.
The launcher argument is required: bundle cannot derive library presence from
checkout IDs (a library has no agent clone). No role, authority or config schema
changes; library project entries are already validated and protected by M9.
## Mount and pin impact (inspection only)
No new imports in broker.mjs or tools.mjs; no RUNNER_FILES or sandbox mount
addition. runtime.mjs is host-only and already imports library index.mjs.
Bundle forwards only a boolean, never a root/mapping/slot/credential. The
existing import-closure tests are bus/tests/project-runtime.test.mjs and
seat/tests/sandbox.test.mjs. Grep found no literal sha256 pin for runtime.mjs
or bundle.mjs; bundle tests derive current source digests through SOURCES.
Library sources remain in the existing whole-directory release copy.
## Runs to schedule only after CLEAR
Set TMPDIR=/mnt/storage/scratch/tmp. Focused new bus/harness files first,
then existing library.test.mjs, library-sync.test.mjs, project-runtime.test.mjs,
harness creative/tools/spec-tools/bundle files and affected CLI launch tests.
Follow with the required whole bus/cli/harness suites and Sage's full shell gate.
No timing claim yet: use the repository/Sage per-file limits and record every
command, outcome, elapsed time and timeout before acceptance. No focused skip
or pattern run substitutes for whole-file gate evidence.
## Read-through findings and residuals
- Library get requires universe although the older design table omitted it;
section 3 isolation rule and C2-05a's accepted API are binding.
- Query type enum matches mapping.TYPES (five values); manuscript is a distinct
lookup and not a find type. Unknown host-path/commit/business/accept fields
are refused; explicit project is restricted to this business's library list.
- Async reads recheck the live holder and current project revision. A stale
read attempts sync, then takes the new served snapshot; a returned fetch
failure/divergence keeps serving the old pin. Busy sync uses the current pin.
Unreadable state, no served commit or unsafe configuration refuse. Query
and sync never hold a SQLite transaction over await.
- Processing halt refuses new agent lookup work and rechecks before returning
async results. Existing human library file/status/sync behavior is unchanged.
- Timer sync and reads still use C2-03a's 60-second contract. No timer tuning or
resource-limit schema is introduced by this row. Q222 creative reply settings
are in C2-06; no reply guard or writing capability is implemented here.
- The generic tool socket's existing 60-second client timeout can precede the
server's 120-second Git timeout. This row does not change the generic timeout;
a timed-out lookup is read-only and retryable, never a write/outcome claim.
- All tests are authored but unrun. No RED, GREEN, syntax, runtime performance,
sandbox, broker/harness suite, shell gate, claim or push evidence exists yet.
## Candidate review checklist (read-through only)
- [x] Five verbs mapped to real C2-05a methods, all require universe.
- [x] Default sole library or explicit owned project; no caller repo/commit.
- [x] Human-only file/status/sync gates kept; lookup holder rechecked.
- [x] Async socket dispatch includes lookup verbs through LIBRARY_VERBS.
- [x] Stale/unknown fetch syncs first; results use current immutable served pin.
- [x] Six/eight creative names per mos-1308; other roles retain their tools.
- [x] Launcher -> bundle -> typedTools passes presence without a library clone.
- [x] No imports added to sandbox source closure; no mount change.
- [ ] Execute new tests, RED characterization, full files/suites after CLEAR.
- [ ] Rebase after rows 253/216 land, under a new branch name.
- [ ] Claim/push only in Sage-authorized gap; Dewey review and Sage gate.
Candidate preservation commits are explicitly unrun under sage-2795; they are
not completion, green-suite or landing claims. No dependencies installed.
Correction: the first committed-range diff --check found a blank line at EOF
in the new library-tools test, missed while it was untracked. Removed in a
follow-up local commit; the complete range must be checked again. No tests ran.
## Shared-log receipts for Sage (sage-2815)
The shared logs are unchanged from the base in this row. Sage appends these
receipts in the landing sweep; they are preserved here, not in the manifest.
For `BUILD-LOG.md`:
```text
2026-10-22T16:36:30Z | Della row224 C2-05b #1725 preparation | Before: C2-05a queries exist at 4f3fbc25a but no broker lookup verbs or typed tools. Scope: broker/tools plus Sage-authorized runtime/bundle integration, tests and docs. Existing human-only library reads retained; five agent queries require a live holder and trusted configured source. Code/docs only under row253 gate; no Node/test/push/claim. Runtime ownership coordinated with Rocko219/Huey272.
```
For `docs/SESSIONS.md`:
```text
2026-10-22T16:36:30Z | Della (sage-2795 / sage-2804, Q220) | Row 224 C2-05b #1725, della/row224 base 4f3fbc25a | Read both pinned briefs and C2-05a source; preparing broker/typed tools and authorized host runtime/bundle seams. No queue command, Node, HTTPS Git, claim, test or push during Gander gate. Dewey reviewer; Rocko/Huey runtime coordination sent. Tests and integration verification deferred to CLEAR.
```
Local-only draft pins 19c0a7adf/7cf39c390 are superseded by the
log-free candidate. Saved as local branch della/row224-draft-with-logs before
rebuilding the unpublished candidate commit; no remote history changed.
## Dewey dewey-0852 and Sage sage-2819 — focused corrections
B1: inline freshness uses max(lastFetch, lastError.at), not lastFetch alone.
An error within 60 seconds counts as a recent attempt, so later lookups serve
the old served pin without another fetch. Keep inline sync: the brief requires
it and businesses without timed libraries have no sync timer. The extended
failed-fetch test makes reads at +1, +59,999 and +60,000 ms (one attempt total),
then +60,001 ms (second attempt). All reads must return the old commit.
Required mutant M-B1: remove lastError.at from recentAttempt. The repeated
reads must fail the attempted-count assertion. This mutant has NOT been run.
Residual (Sage will defer): the first inline fetch in each outage window can
still exceed the tool timeout (60 seconds, or the runner broker client’s
30 seconds). Bounding that fetch is out of scope for 224. Subsequent reads
are paced by persisted lastError.at. No claim of first-read outage latency.
N1: whole bus, cli and harness suites are required, plus the shell gate.
If the new thread-launch integration test skips due to sandboxProblem(),
name the skip; a skip is not launcher integration proof.
N2: cli/tests/thread-launch.test.mjs now depends on library/tests/helpers.mjs
mappingEntry() and library/tests/fixtures/mapping.json. Changes to that fixture
must rerun the CLI launch file.
N3: harness/tests/library-tools.test.mjs compares the type enum to mapping.mjs
TYPES, so adding a mapped type cannot silently leave the typed tool behind.
These edits are local code/docs only. No Node, RED/GREEN run, mutant, claim,
HTTPS Git or push. New local pin goes to Dewey/Mos/Sage for focused review.
+196
View File
@@ -0,0 +1,196 @@
# Row284 C2-05c — supporting scope and served-tree blob (#1789)
Owner Della; reviewer Dewey; gate Sage. Build start sage-3658/3660 (10-23).
Branch della/row284. Original prerequisite base Honker28346bad7ea3;
merge fc05bcbc3 carries22423816cf28 and2164f3fbc25a intact. Only conflict was
CLI thread-launch's added footer tests: retained both full test bodies and
assertions. No prerequisite code copied. Every prerequisite needs its own
review/mutants/landing under mos1351; rebase after283 lands.
## Execution status
UNTESTED. No Node, node--check, test or mutant run before Sage CLEAR. First
own commit is the authorized old-test migration; the implementation follows.
Sage authorizes an immediate untested push. No queue write or shared-log edit.
224 stays first;284 is critical to Bubba288. T4 for224 is not a CLEAR yet.
## Binding readings
Brief C2-05c at94c33a43c:1737-1789, Q226, sage2918 and mos1330 item3.
get defaults canon; explicit playground/supporting opt-in; mismatch null.
Sort full outgoing/incoming lists supporting, playground, canon before paging,
keep cross-scope links and universe filter. Manuscript stays canon/playground.
No all/array scope, write tool, guard, mapping or creative-name-count change.
## mos-1330 old-test migration (separate commit)
All paths below are packages/library/tests/query.test.mjs. Baseline file at
fc05bcbc3 matches216's file. Source assertions retained; negative calls receive
explicit scope too, so universe isolation cannot pass on default-scope denial.
Call/assertion 348 -> 348:
```js
// OLD
const rin = await get(s, { universe: 'idea', path: 'Drafts/New World/Characters/Rin.md' });
// NEW
const rin = await get(s, { universe: 'idea', path: 'Drafts/New World/Characters/Rin.md', scope: 'playground' });
```
Call/assertion 350 -> 350:
```js
// OLD
assert.equal(await get(s, { universe: 'harbor', path: 'Drafts/New World/Characters/Rin.md' }), null);
// NEW
assert.equal(await get(s, { universe: 'harbor', path: 'Drafts/New World/Characters/Rin.md', scope: 'playground' }), null);
```
Call/assertion 366 -> 366:
```js
// OLD
assert.equal(await get(src(ref, mapping(WITH_BLEND)), { universe: 'blend', path }), null);
// NEW
assert.equal(await get(src(ref, mapping(WITH_BLEND)), { universe: 'blend', path, scope: 'playground' }), null);
```
Call/assertion 367 -> 367:
```js
// OLD
const own = await get(src(ref), { universe: 'harbor', path });
// NEW
const own = await get(src(ref), { universe: 'harbor', path, scope: 'playground' });
```
Assertion 349 retained, OLD == NEW:
```js
assert.equal(rin.scope, 'playground');
```
Assertion 368 retained, OLD == NEW:
```js
assert.equal(own.scope, 'playground');
```
The null assertions at350 and366 are the complete OLD/NEW expressions above:
expectation remains null. Added one separate no-scope playground-get -> null
behavior test. It is unrun and expected to expose old get behavior until the
new scope implementation lands. This migration is authorized by mos1330 under
mos1272; no check removed or weakened. Dewey checks this list in manifest review.
## 283 surface and blob choice
Inspected actual46bad7ea3: optional editor.user is present. reader's parsed
file records and index entries still discard tree oid. In owned query.mjs use
the existing bounded listTree(repo, servedCommit) to obtain exact path oid,
not HEAD and not a user-supplied revision expression. No reader/index edit.
Tradeoff: one additional bounded tree listing per get; latency unmeasured.
Recheck after rebasing to283's actual landing.
## Intended owned paths
query.mjs; broker.mjs lookup branch; tools.mjs lookup schemas/descriptions;
library query.test.mjs/query-fixture.mjs; bus library-lookups.test.mjs;
harness library-tools.test.mjs; this BUILD.md. Other files are prerequisites.
## Gate
Dewey manifest approval, whole library/bus/harness suites and every
scripts/test-*.sh on Sage gate. Before review grep all package tests for enum
pins and canon/playground pairs. Keep shared logs out; send receipts to Sage.
No test or mutant evidence claimed. Manifest/digest follows complete code.
## Implementation head (UNTESTED, sage-3658)
- query scope accepts supporting for find/search/get. get defaults canon,
mismatch null. Manuscript passes a separate two-value scope allowlist.
- get returns blob from listTree(source.repo,index.commit) after scope/universe
filtering. The same index.commit supplies the text; source HEAD is irrelevant.
- Full outgoing and incoming link lists are stably sorted by supporting,
playground,canon before combining/paging. All visible cross-scope links stay;
universe/hybrid visibility still applies. Scope metadata isn't added to links.
- Broker only adds optional scope to library.get's strict fields. No new verbs.
- Typed scope expands find/search/get only; manuscript retains its own two-value
enum. Names/M3 counts remain the row224 six/eight, no write tools added.
- New tests: book/universe/draft supporting find/search/get; defaults/mismatch;
absent supporting prefix; source HEAD advances but each scope's blob equals
served:path; 300 outgoing plus300 incoming links ordered across bounded pages,
retaining cross-scope links and dropping other-universe links; hybrid source
stays canon-only; no all/array scope; manuscript rejects supporting; broker
scope/blob forwarding; typed enums and unchanged names. All unrun.
## Additional old assertion pin (brief Gate)
harness/tests/library-tools.test.mjs:49 at23816cf28 (line49, unchanged in this head):
OLD find scope enum ['canon','playground']; NEW ['canon','playground','supporting'].
This is the exact scope-enum pin the brief directs updating. Existing six/eight
name snapshots and TYPES equality assertion are unchanged. The new manuscript
pin intentionally remains ['canon','playground']. No other two-scope test pin
was found by rg over packages/*/tests at this build's prerequisite merge.
## Proposed mutation evidence (reviewer confirms final set)
All unrun. Require assertion failures with whole-file runs and pristine restore:
M1 remove supporting from query SCOPES -> supporting lookup cases.
M2 drop get's requested-scope comparison -> default/mismatch null cases.
M3 use source HEAD for blob lookup -> served-tree/advanced-HEAD equality case.
M4 remove link rank sort -> multi-page outgoing/incoming expected order.
M5 restrict links to requested scope -> cross-scope link counts/order case.
M6 drop sees() from visible/link filtering -> second-universe/hybrid cases.
M7 allow supporting in manuscript validator -> manuscript refusal case.
M8 omit get scope from broker fields -> real broker supporting get case.
M9 widen typed manuscript enum -> typed-scope separation case.
## Inspection and pending gate
No production imports change in broker/tools, so no new sandbox closure file
or mount. query.mjs is host library code and adds existing reader.listTree;
no new package/export/copy-path file. rg found the one old find scope pin plus
the intentional new manuscript two-scope pin. Whole library/bus/harness and
shell gates remain required. CLI integration/merge context is prerequisite224/
219, not this row's source delta; any changed gate expectation goes to Sage.
Node checks, tests, mutants and elapsed-time evidence remain absent pending
CLEAR. The first test-migration commit and this implementation are distinct.
Sage sweep receipt: Della row284 builds on28346bad7ea3 plus intact224 merge;
mos1330 old-test migration separate, supporting scope/blob implementation and
fixtures authored, completely UNTESTED. No shared-log/queue write or live action.
## Current assertion anchors after implementation imports
The separate migration commit0ed4fbab9 retains its complete OLD/NEW text
above. Adding only the fs/path imports in the implementation shifts anchors:
query.test.mjs:130: `const blendCh2 = await get(src(ref, mapping(WITH_BLEND)), { universe: 'blend', path: 'Book One/Chapters/Chapter 02.md' });`
query.test.mjs:350: `const rin = await get(s, { universe: 'idea', path: 'Drafts/New World/Characters/Rin.md', scope: 'playground' });`
query.test.mjs:351: `assert.equal(rin.scope, 'playground');`
query.test.mjs:352: `assert.equal(await get(s, { universe: 'harbor', path: 'Drafts/New World/Characters/Rin.md', scope: 'playground' }), null);`
query.test.mjs:368: `assert.equal(await get(src(ref, mapping(WITH_BLEND)), { universe: 'blend', path, scope: 'playground' }), null);`
query.test.mjs:369: `const own = await get(src(ref), { universe: 'harbor', path, scope: 'playground' });`
query.test.mjs:370: `assert.equal(own.scope, 'playground');`
No result/null assertion changed; scope arguments carry the explicit opt-in.
## Dewey dewey-0941 static review — round2
Static scope fc05bcbc3..47db04eba CLEAN; not a final verdict or test evidence.
M1-M9 accepted as the final set. Optional M10 not added to this gate.
N2 taken: keep the existing canon-path supporting-scope mismatch assertion,
and ADD get(Book One/Support/Notes/Helper.md,scope:supporting) -> null under
the default mapping with no supporting prefix. The supporting-positive test
already returns this same path under withSupporting, so the added probe
pins the missing-prefix case directly. No assertion removed or weakened;
mos1330's separate migration commit and its retained assertions unchanged.
N1 proposed DEFERRED/follow-up for Sage: retain each tree oid in reader/index
entries so get can return blob without cat-file/whole ls-tree -r -l per page.
Current get's result is pinned correctly, but per-page cost grows with the
library. No performance measurement yet. This optimization needs reader/index
ownership and separate acceptance/performance evidence; do not silently expand
284's manifest or change its served-snapshot contract for it.
No Node, tests, mutants or syntax checks run in this round; hold still applies.
@@ -1,30 +0,0 @@
# Row 239 round 1 verdict: honker/row239-r4 e0cb1db2c (duckworth-0295)
Request: honker-0206 (in-review round 1, rev 3719). Candidate: e0cb1db2c on refactor 546366f4a.
Method: git reads of local objects only. No node, no tests, no fetch, no queue reads.
## Verdict: APPROVE
- **The replay is exact.** `git range-diff 93694c518..5b8492b0f 546366f4a..0cae2dad5` gives four `=`
lines. None of the 16 manifest paths changed between 93694c518 and 546366f4a, and
`git diff 5b8492b0f 0cae2dad5` over those 16 paths is empty. My r3 read (REBASE-r3-5b8492b0f.md,
duckworth-0292) applies unchanged, and so do the 0165 design approval and the 0168 code approval with
C1 and C2 met.
- **e0cb1db2c is doc only.** It appends 26 lines to agents/honker/work/queue-239/BUILD.md. No test reads
that file.
- **Pins at e0cb1db2c.** All four 64-hex pins in channel-proof.test.mjs match the files' sha256:
human.mjs, human-cli.mjs, transport.mjs and webui bus.mjs (e3f52172…). Upstream changed library
src/index.mjs and business src/index.mjs, and no test or script pins them. The only hit in my grep
is a basename collision (`cli.mjs` matched `human-cli.mjs`).
- **The run, as Honker reported it (sage-3773 CLEAR, on 0cae2dad5. I didn't rerun it):**
- The six touched test files ran whole: 65/65, 0 skipped, no first-run red.
- Mutants M1-M3g: 9/9 killed as assertion failures in their named tests.
- M3f killed T6, which matches my static trace in 0292.
- Every restore matched by sha256, and the tree was clean afterwards.
Running on the replay rather than r3's base was right, because bus runtime.mjs imports library
src/index.mjs and the cli imports business src/index.mjs, and both changed upstream.
- **What remains is Sage's.** The full gate runs on the replay at landing (sage-3730). Sage's call.
## Notes
- None new. 0168 N1 (endsWith access-revoked) stands as recorded.
@@ -1,74 +0,0 @@
# Row 323 round 1 verdict: goldie/row323 6a0f0f4f8 (duckworth-0300)
Request: sage-3865 (early look at draft 571376c30) and sage-3870 (candidate 6a0f0f4f8, base dd6cb5872,
manifest 903ef0225f2f…cda79f). Brief: docs/plans/2026-10-23_case1-business-file-brief.md, section "Brief:
the Case 1 factory business file".
Method: static. Git reads of local objects only. No node, no tests, no fetch, no queue reads.
## Verdict: APPROVE (static), with one condition
- **The manifest matches.** MANIFEST.sha256 hashes to 903ef0225f2f…cda79f, and both of its blobs match at
6a0f0f4f8: the JSON 85e6b9ac… and the test aecd1c0f…. Neither file changed between 571376c30 and
6a0f0f4f8. The rest of that range is BUILD.md, logs, BUILD-LOG and SESSIONS. dd6cb5872 is an ancestor.
No existing test or source file changed.
- **Fields against the brief and the cited rulings:**
- id `mosaic-stack` (DESIGN section 7). `vars.bus.tester` true. There is no `library` key and no
library project.
- human `jason`. I can't read mos-1554 myself, so this rests on Sage's relay in sage-3823. BUILD.md is
explicit that this is the only literal value that comes from mos-1554.
- The project root /srv/mosaic/src/mosaic-stack is under the DESIGN home /srv/mosaic, and the value
comes from sage-3822. It passes the normalized-absolute check at business.mjs:274. A missing project
file there only gives a warning in validate (cli.mjs:73-77), so the path doesn't need to exist at
install.
- Roles are chief (chief-of-staff) and assistant. The assistant is needed because checkLaunch requires
`launch.instances` to be nonempty and to exclude the launcher (business.mjs:186-190). BUILD.md names
that check, as the brief asks. Both arbiters are chief, under sage-3820. launch.by is chief, which
F7 (case1-accept.sh:545-559) reads.
- Claude only. `plugins.harness` is [claude-code] and both roles use harness claude-code. There is no
pi.
- No tracker, no credentials, no library, and no credential value or reference anywhere in the file.
Q247 covers the tracker by omission.
- **The model, provider and effort have sources.**
- The model claude-opus-5-5 is Sage's ruling in sage-3822.
- The provider mapping anthropic/claude-opus-5-5 matches packages/business/README.md:185.
- `thinking: high` is one of the claude-code LEVELS (index.mjs:28) and matches the chief convention at
session-launch.test.mjs:83.
- With no `launch.limits`, there is no ceiling: limitHit (launch.mjs:137-155) checks only the limits
that are defined. That is a valid choice, and N1 records it.
- **The tests read the shipped bytes.**
- DOCUMENT is `REPO/docs/guides/case1-business.mosaic-stack.json`. Case 1 writes those exact bytes,
loads them through loadBusiness, checks the bytes didn't change, and then asserts every field.
- The validate, install, boot and refusal cases copy the document and change only the project root,
under sage-3820. Since an absent root only warns, the copy differs from the shipped file only in a
way validate doesn't refuse.
- The brief's three tests are all there:
- (1) The loader case.
- (2) The real case2-install.sh factory profile with `--business-file`, with only stack-copy stubbed.
- (3) `bus-tester-off`, the literal library key through the final validate (sage-3820), and the
library project through `profile-library-business`.
- The boot case also covers the 324 boundary: bootConfig gives no trackers, credentials.status() is
[], and the reader's credentials are [].
- **The tester-switch mutant is a real kill target.**
- mutant.py asserts that the needle `"bus.tester": true` occurs exactly once, flips only that value,
and restores in a finally block with a byte-equality check.
- The value is asserted at test :66. mutant.log shows `not ok 1`, `false !== true`, `# fail 1`, and the
restored sha256 85e6b9ac… equals the manifest.
- The flip would also make the install case at :95 fail, because the install refuses with
bus-tester-off.
- **The run, as Goldie reported it (sage-3869 CLEAR, 31-byte TMPDIRs, on 571376c30). I didn't rerun it.**
- The new file passed 7/7, the business suite 261/261, and the restored file 7/7 again.
- The mutant was killed.
- The earlier revoke-window red is the tools.sock length limit (session.mjs:278, dda2d07b4). It fails
the same way on the bare base dd6cb5872, so it isn't this row's.
## Condition
- C1. Under mos-1561, the cli suite counts only from Sage's full gate on 6a0f0f4f8. This approval
depends on that gate passing with no new failures.
## Notes
- N1 (doc). `launch` has no `limits`, so the business has no session ceiling. If Mos wants a cap on .53,
it's a later launch-settings edit and doesn't need a new file.
- N2 (doc). An install on .53 with no project file at /srv/mosaic/src/mosaic-stack still passes. It
warns "no project variables" (cli.mjs:76), which the run sheet could mention.
@@ -1,56 +0,0 @@
# Row 324 round 1 verdict: goldie/row324-r2 dbdfcfd52 (duckworth-0298)
Request: sage-3853 (#1820, comment 31469, round 1, rev 3768/3769). Candidate: dbdfcfd52 on refactor
3569bf54f, code commit 7e59ba8b3. Brief: docs/plans/2026-10-23_optional-tracker-brief.md.
Method: static. Git reads of local objects only. No node, no tests, no fetch, no queue reads.
## Verdict: APPROVE (static)
- **The manifest matches.** agents/goldie/work/queue-324/MANIFEST.sha256 hashes to 52874356…2b473d,
the digest Sage gave. All 8 listed blobs match the files at dbdfcfd52.
- **The loader change is narrow.**
- business.mjs:239 drops "tracker" from the required-key loop.
- business.mjs:296 is `Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null`.
Omission gives `tracker: null`. A present key always goes through checkTracker.
- business.mjs:298 guards the role tracker comparison on `tracker &&`. An absent business tracker
doesn't waive the role pm tracker check, and the new test covers that.
- **A present null still refuses.** checkTracker (business.mjs:85) calls requireObject first, so a
present null, false, [] or a non-object refuses. {} and {sync:{}} refuse further in. The new
refusal test covers each of them.
- **Every business.tracker reader handles absence.** I read each hit in Goldie's reader-audit.txt and
the two outside it in scripts:
- bus runtime.mjs:131 registers `<business>/@sync` only under `b.tracker?.sync?.credentials`. An
absent tracker registers nothing.
- bus runtime.mjs:247 hashes `b.tracker?.sync ?? null`.
- broker.mjs:6242 reads `b.tracker?.sync?.bot`. Broker 3969 and 4497 read the role-level tracker
only.
- cli.mjs:85 and webui reads.mjs:166 use `tracker?.sync?.credentials ?? {}`. The
`b.tracker.sync.bot` inside the reads.mjs loop runs only when there are entries.
- cli config.mjs:58 trackerFor returns null on an absent tracker before the vars check. bootConfig
(config.mjs:138-139) sets config.trackers only for a non-null result.
- tasks/adapter.mjs loads only for configured trackers (process.mjs:136), and its settings refuse
with tracker-config at :25 before the :35 and :81 reads.
- instance-settings.mjs:127 reads `old.tracker?.sync`.
- stack-release.sh:2074 and :2120 use `b.tracker?.sync?.credentials`.
- launch-settings, resolve, instances.mjs and public/bus.js:785 read role-level trackers only.
conversation/controller exec.tracker is a different object.
- **Writers don't serialize the loader's null.** The setters build `next` from the raw file bytes
(organization.mjs:88-93), not from the loaded object, and validate before writing
(organization.mjs:201, :208). A business file without a tracker stays without one.
- **The brief's tests are there.**
- business/tests/optional-tracker.test.mjs has 5 tests: omission loads null, CLI validate with the
sync file gone, a present tracker preserved and frozen, the refusal set, and the role pm check.
- cli/tests/optional-tracker.test.mjs has 4 tests: no trackers from bootConfig with tracker vars
set, startBroker with no @sync credential, businessView with no credentials, and a present
tracker with a missing token refusing credential-file.
- The existing-tracker case runs through business.test.mjs unchanged apart from :98.
- **The one old-test edit is in bounds.** business.test.mjs:98 drops "tracker" from the
required-key loop, as mos-1556 allows.
- **The run, as Goldie reported it (sage-3850 CLEAR, I didn't rerun it):** 5/5, 4/4 and
business.test 21/21. The presence mutant at business.mjs:296 is killed, and the restore matched
sha256 e76db421.
## Notes
- N1 (doc). The 'undefined' refusal case only exists in memory. A JSON file can't carry undefined,
so on disk it is the same as omission. The test is still correct.
-8
View File
@@ -1,8 +0,0 @@
# Fethry
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 225 (C2-10b, handed over from Morgana).
No launcher in this directory. The seat runs as a T3 thread (thread 14f80e3c) and works in its own worktree. It has no
credentials of its own; it pushes only its own `fethry/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-9
View File
@@ -1,9 +0,0 @@
# Gladstone
Builder seat, Codex gpt-6.1-sol, started by Mos (mos-1531, mos-1532). Owns the Case 1/2 host fixes for the 433 P1 rerun,
F1 to F6 (#1816, Vikunja 614).
No launcher in this directory. The seat runs as a T3 thread (thread 433058fc) and works in its own worktree. It has no
credentials of its own; it pushes only its own `gladstone/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-304
View File
@@ -1,304 +0,0 @@
# Hostcheck item 5 and bundle verification (Gitea #1816, Vikunja 614)
Queue row pending from Sage. Owner Gladstone, reviewer Morgana, gate/landing Sage.
Base: fetched origin/refactor 93694c5185d0a7f989d2fe78fb620e44599b744e;
branch gladstone/hostcheck-item5. Scope: mos-1531 / ops-0256, sage-3738 and sage-3740.
## Before and after
Ubuntu 24.04 dpkg 1.22.6ubuntu6.6 prints `root mosaic 750 /usr/bin/bwrap`.
Item 5 compared the literal `0750`, falsely refusing a correctly configured host;
--enforce would unload the profile, close bwrap and record DRIFT.
The candidate accepts exactly four fields, root, mosaic, octal 0750, and $BWRAP.
`0*750` represents all octal spellings numerically equal to 0750 without arithmetic
conversion/overflow. Horizontal whitespace separates fields; extra fields and
embedded newlines refuse. Owner, group and path remain exact. The stat comparison
and existing output strings remain unchanged. Only the item 5 comment also changes.
The two release-transport bundle checks previously ran outside any repository.
Each now creates an empty bare scratch repository, verifies with git -C, and removes
the scratch directory using an EXIT trap. A failure exits the shell before cloning
or fetching. No other run-sheet text changes.
## Tests and mutants
Only packages/cli/tests/case1-hostcheck.test.mjs changes among tests, by appending
three new test() blocks; the existing passing 0750 fake and failing fake are intact.
The pass test exercises 750, 0750 and 000750. The enforce test checks no unload,
chown, chmod, DRIFT or hostcheck log for 750. Refusals independently cover 755,
0755, 4750, 75, 7500, 075, non-octal strings, other owner/group/path, missing path,
extra fields, multiple lines, blank/empty output and a missing fixture file.
| Mutant | Change | Killer | Result |
|---|---|---|---|
| M1 | Restore the old literal root mosaic 0750 comparison (drop 750) | new check pass test | Unrun |
| M2 | Accept any mode while preserving owner/group/path | new refusal test | Unrun |
Mutant procedure: preserve script bytes, assert one replacement, run the named
killer alone, require failure for its intended assertion, restore in finally and
check the original bytes/hash. Run the original targeted file again after restore.
## Verification status
Untested. Sage-3738 prohibits all Node until CLEAR because the joint full gate
runs alone. Sage-3740 explicitly directs pushing this untested build for Morgana's
static review before CLEAR. This supersedes test-first execution for this draft;
no Node, tests, node --check, full suites or live-host operations have run.
Queue next refused linked-worktree execution; Sage owns queue creation/movement.
Push only gladstone/*, with MOSAIC_GITEA_CREDENTIAL_FILE unset; no amend/force.
Static draft receipt: `git diff --check` passed. Test-file numstat against the
base is `46 0 packages/cli/tests/case1-hostcheck.test.mjs` (added/deleted).
## Scope and implementation amendment (mos-1534, sage-3741)
F1 correction: Sage requires precisely `750` or `0750` and exactly single spaces,
so the candidate now uses `0?750` and literal spaces. It refuses 00750/000750,
a doubled space and a tab. This supersedes the initial numeric-equivalence design
above. Only this row's own appended tests change; no base test line changes.
F3: case2-unit.sh plain() adds @. abs_plain is its only caller and requires an
absolute normalized path, so @ cannot become an ExecStart prefix. In sed @ is
ordinary replacement text (the delimiter is |); &, backslash and | still refuse.
Environment=MOSAIC_CLAUDE_BIN carries @ literally; the parser compares the complete
value, with no shell evaluation. @anthropic-ai also cannot match the residual
@[A-Z_]+@ token check. Tests render/reinstall with a regular executable under
@anthropic-ai, checking Environment and the new copy's WorkingDirectory. Since a
nonroot test cannot create root ownership, a stat fake models root:0755 only for
that file and its ancestor chain. The real file type/executable checks, render,
installed-unit parser, and all other stat calls remain active. A separate block
refuses space, %, $, both quote characters, backslash, & and |.
F4: copy_tree changes to / before archive/npm/find, after all its path arguments
are absolute. copy_bundle retains its existing relative-bundle resolution before
calling copy_tree. Checkout copies have absolute REPO and COPIES too. Source-only
use from stack-release.sh changes no cwd because copy_tree is not called there.
Audit of every AS_USER command in stack-upgrade.sh: fetch and merge use git -C;
plan, gate, revert, confirm-rollback, stage (activate/rollback), confirm and rehearse
invoke stack-release.sh, which changes to its checkout at line 34. No other
AS_USER command needs a fix. The new test enters a directory mode 0111, verifies
that ls cannot read it, then runs both a relative-bundle copy and checkout copy,
asserting success, no find restore error, and an immutable completed copy.
Additional mutants (unrun under the same no-Node hold):
| Mutant | Change | Killer | Result |
|---|---|---|---|
| M3 | Drop @ from plain() | new factory scoped Claude-path test | Unrun |
| M4 | Remove copy_tree's cd / | new unreadable-cwd copy test | Unrun |
All four fixes remain untested. Follow-up is an additive commit on the pushed
branch, per sage-3741, with no amend or force. Morgana's static F1/F2 read of
f30c74b12 was CLEAN (morgana-0205); F3/F4 and execution remain open.
Morgana N1: the outer bundle-verification failure now prints
`STOP: bundle verify failed; do not continue` instead of exiting the interactive
root shell. The subshell still returns failure and cleans up. This follows the
sheet's human read-and-stop convention and avoids sending remaining pasted lines
to a parent shell after exiting sudo -i. Inner mktemp failure still exits only the
subshell. This supersedes the initial outer-shell failure behavior noted above.
## File numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 4 | 0 |
| `agents/gladstone/work/hostcheck-item5/BUILD.md` | 114 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 12 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 29 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 16 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
## F5 (mos-1536 / ops-0260): explicit notifier decision
Before: no run-sheet step creates the required notifier decision, and precheck
accepts a host whose bus will refuse missing notify.json. After: section 9a
requires the mosaic user to create its business notify directory at 0700 and an
explicit {"notifyVersion": 1, "binding": null} file at 0600 before section 10.
The shell runs with cwd / and umask 077; noclobber refuses an existing decision.
Named binding is explicitly a later per-host choice requiring Mos's go.
case2-install.sh is unchanged and makes no silent default.
case2-precheck.sh's existing facts helper dynamically imports readNotifyConfig
from cli.mjs and calls it directly. File type, uid, exact 0600 mode, JSON keys,
version and binding-name validation therefore stay identical to the bus. The
shell checks the business notify directory with existing private_dir (same uid,
not a symlink, exactly 0700) and prints the new notifier config line. No notifier
is started and no configuration is written. Tests append pass cases for null and
a valid named binding, and refusal cases for missing file, 0644, 0755 directory,
extra key, malformed JSON, invalid binding/version and a symlinked file.
M5 (unrun): remove the notifier shell check, including its refusal branch; the
new notifier refusal test must fail. Also include Morgana's F3 negative mutant
M3b (allow $ in plain), killed by the new unsafe-character test.
Existing positive precheck tests need an explicit notify fixture. Approval
requested via Sage in gladstone-0006/-0007 for minimal setup-only additions to
case2-unit.test.mjs host(t) and case2-install.test.mjs world(t); assertions remain
unchanged. Those additions are not yet applied. Correction to gladstone-0007:
case1-accept.test.mjs also names case2-precheck.sh, but uses a stand-in; it does
not execute the changed script and needs no fixture amendment.
Execution remains held by Sage: no Node/tests run. Morgana-0207 static F1-F4
verdict on a75532db1 is CLEAN; execution and F5 review are still open.
F5 acceptance from mos-1536 (for review):
1. Before section 10, as mosaic, explicitly create <dataRoot>/notify/<business>
at 0700 and notify.json at 0600 with default binding null; a later named
binding needs Mos's go. Do not make case2-install.sh write it.
2. Precheck must refuse missing file, non-0600 file, directory looser than 0700
and a document outside the CLI's exact shape. Reuse/match cli.mjs validation,
and name the new line in this run-sheet step's Precheck entry.
3. Append-only test blocks cover pass, missing, 0644, 0755 dir, extra key; record
a skip-check mutant, the touched test file and its numstat.
4. F3/F4 precede F5, each follow-up commit stays on gladstone/hostcheck-item5;
send pushes to Sage/Morgana with Mos cc. No Node until Sage CLEAR.
Static F5 draft receipt: git diff --check passed. Existing fixture approvals
remain pending; F5's new pass test creates its own explicit decisions. No claim
that the old positive precheck tests pass without the pending setup changes.
## F5 candidate file numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 6 | 0 |
| `agents/gladstone/work/hostcheck-item5/BUILD.md` | 179 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 34 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 65 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 16 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-precheck.sh` | 19 | 0 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
## Fixture approval and separate follow-up (mos-1537)
Mos approved exactly setup-only additions to case2-unit.test.mjs host(t) and
case2-install.test.mjs world(t): each gets three added lines for a 0700
notify/acme directory and 0600 notify.json with {"notifyVersion":1,"binding":null}.
No existing line, fake or assertion changes or is deleted. F5's missing-file
negative case removes the file after setup, so the new default cannot mask it.
The newly appended install test checks preserved bytes and mode after success,
and separately removes the decision before install and checks it stays absent.
case1-accept.test.mjs is unchanged. This follow-up is committed separately on
33950cf9b as directed, with no amend. No Node/tests run; suite claims remain open.
Morgana-0211 found F5 source/run sheet CLEAN statically; the setup prerequisite B1
is addressed by this approved fixture patch, pending execution.
Additional planned mutants from Morgana-0211 (unrun): M5b drops private_dir,
killed by the directory fault; M5c prints NOTIFY ok unconditionally, killed by
the document faults. Existing test preservation is checked by git diff showing
only additions (numstat below).
Fixture follow-up numstat: case2-unit.test.mjs +3/-0,
case2-install.test.mjs +18/-0. Against the original base: case1-hostcheck.test.mjs
+48/-0, case2-unit.test.mjs +68/-0, stack-copy.test.mjs +16/-0,
case2-install.test.mjs +18/-0. git diff --check passed.
## F6 and notify parent correction (mos-1537 / ops-0261)
Before: install creates a copy but no human CLI wrapper; the sheet's
stack-copy.sh check --units external therefore fails even with correct units.
After: the sheet explicitly places a wrapper step after successful install and
before that check. On Mos's separate live-step go, mosaic changes cwd to /,
creates $HOME/.npm-global/bin if absent, then invokes its clone's stack-copy.sh
wrapper <sha> --create. Existing wrappers refuse and require review; install is
unchanged. This is documentation of a gated host action, not execution of it.
There are no tests pinning these sheet steps to adjust.
Section 9a now explicitly creates/chmods <dataRoot>/notify and then its business
leaf, each 0700. This addresses ops-0261's observation that mkdir -p -m only
controls the leaf. The existing umask 077 remains. All exits are inside the
unprivileged shell, so no failure exits the operator's interactive root shell.
Static receipt: git diff --check passed. No Node, tests or live action. F6 and
this parent-directory documentation correction are a separate commit after the
approved fixture-only 5fd2328b6 follow-up. Reviewer should retain the earlier
F1-F5 source verdicts and inspect this delta. Queue row still pending from Sage.
## F6 candidate file numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 10 | 0 |
| `agents/gladstone/work/hostcheck-item5/BUILD.md` | 241 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 51 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-install.test.mjs` | 18 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 68 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 16 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-precheck.sh` | 19 | 0 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
## F6 gate citation (sage-3747)
Added scripts/stack-copy.sh:3 to the F6 wrapper step's explicit Mos-go requirement.
This is doc-only; commands and tests are unchanged. Sage ruled the delete-after-
setup missing-file negative meets mos-1537, so no fixture change is needed.
Morgana-0213 found the fixture/F6 deltas CLEAN statically. No Node/tests run;
Morgana's eventual run waits for Sage's row and CLEAR after the 220+216 landing.
Numstat for this follow-up: host-run-sheet.md +1/-1; BUILD.md +9/-0.
Static receipt: git diff --check passed.
Correction: 0f3d874d4 added 10 BUILD.md lines, not 9; the git numstat is authoritative.
## First review run and F4 test correction (morgana-0217)
Morgana ran af201b4a9 (6fa47e07d only relocates this file) under her authorized
slot with timeout -k 10 and TMPDIR under /mnt/storage/scratch/tmp. Receipts:
/mnt/storage/scratch/morgana-g320-out/base-summary.txt and mutants-summary.txt.
| Targeted file | Passed | Failed |
|---|---:|---:|
| case1-hostcheck.test.mjs | 19 | 0 |
| case2-unit.test.mjs | 47 | 0 |
| case2-install.test.mjs | 24 | 0 |
| stack-copy.test.mjs | 17 | 1 |
Seven of eight mutants were killed: M1 literal0750, M2 anymode, M3a drop@,
M3b allow$, M5 drop shell block, M5b drop private_dir, M5c always NOTIFY ok.
M4 drop cd / SURVIVED: base and mutant both failed only setup's cleanup hook.
This is a test defect, not a green acceptance result. The 0111 cwd retained
search permission and did not reproduce the inaccessible inherited cwd. It
also blocked setup's chmod -R cleanup because the directory remained unreadable.
B1 correction touches only this row's appended F4 test: the child shell enters
an initially searchable directory, chmods it 000, checks both ! -r and ! -x, then
execs stack-copy. The parent asserts all read/search bits remain cleared. The
bundle now lives outside cwd and is passed absolutely, matching the real flow;
existing row232 coverage still tests relative bundles from an accessible cwd.
Both bundle and checkout paths remain covered.
B2 correction: each child run/assertion sequence has a finally that restores the
cwd to0700 before setup's existing cleanup. No old setup/test lines change.
Production scripts are unchanged; the cd / fix remains correct by static review.
Correction is untested locally: no builder Node/test CLEAR was given. Morgana
will rerun stack-copy whole, M4 and residue cleanup on the pushed correction;
other files need no rerun unless changed. git diff --check passed.
## F4 corrected candidate numstat against 93694c518 (added / deleted)
| File | Added | Deleted |
|---|---:|---:|
| `BUILD-LOG.md` | 12 | 0 |
| `agents/gladstone/work/queue-320/BUILD.md` | 304 | 0 |
| `docs/SESSIONS.md` | 1 | 0 |
| `docs/guides/host-run-sheet.md` | 51 | 2 |
| `packages/cli/tests/case1-hostcheck.test.mjs` | 48 | 0 |
| `packages/cli/tests/case2-install.test.mjs` | 18 | 0 |
| `packages/cli/tests/case2-unit.test.mjs` | 68 | 0 |
| `packages/cli/tests/stack-copy.test.mjs` | 22 | 0 |
| `scripts/case1-hostcheck.sh` | 2 | 2 |
| `scripts/case2-precheck.sh` | 19 | 0 |
| `scripts/case2-unit.sh` | 1 | 1 |
| `scripts/stack-copy.sh` | 3 | 0 |
-171
View File
@@ -1,171 +0,0 @@
# Row 321: F7 wrapper environment and F10 first adoption (#1817)
Owner Gladstone; reviewer Boyd (separate Codex thread); queue/review/landing Sage.
Authorized by sage-3791. Branch gladstone/row321 starts at fetched origin/refactor
833ec1cc8d7c11842efb82e57c1a0b256c8d22d7. The row320 branch is unchanged.
Brief: docs/plans/2026-10-23_host-fixes-1816-brief.md, section
"Brief: F7, the wrapper step's PATH and umask under sudo", including F10;
verified blob 7333f94ba9cfd58f87c5a3580d428b84c65845b6 (requested pin 7333f94ba9cf).
Only the run sheet and this BUILD.md change. No scripts, tests, config, credentials,
queue state, deployment or live host operation changed. No tests or Node commands run.
## Before and after
F7: sudo secure_path omits the pinned Node directory, so wrapper creation reaches
instance.sh's bare node and exits127. An inherited umask can leave the bin dir0775.
The MOSAIC_WRAPPER heredoc now sets umask077 and the sheet's existing pinned Node
PATH before mkdir. One sentence names secure_path and why the step sets both.
The existing Mos go remains explicit. No wrapper command is executed here.
F10: first install omits adoption, so the first activate refuses no-pointer.
The release-transport section now places explicit adoption after both units are
running (section10) and before any activate/upgrade, run as mosaic on Mos's go by
Jason or Mos, citing stack-release.sh:14. It uses the configured dev instance and
external units, with the same pinned Node PATH under sudo.
Adjacent to the upgrade command, the sheet now says only the first upgrade after
adopt takes --no-rehearsal: adopt writes no backup; missing flag refuses no-backup;
later upgrades omit it, because a backup with that flag refuses rehearsal-required.
Source checks: stack-release.sh pointer_for_business refuses no-pointer and directs
adopt; stack-upgrade.sh rehearse handles rc3/no-backup and rc0/rehearsal-required.
This records existing behavior and authority; it adds no requirement or live go.
## Test-reference grep (negative and positive control)
The required negative grep covers both F7 and F10 because neither is read by any
matched test. The positive control locates the precheck path that the unit/install
tests actually execute, showing the pathspecs reach their test files.
Negative grep:
```sh
git grep -n 'host-run-sheet' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit 1
# no matches
```
Positive control:
```sh
git grep -n 'case2-precheck.sh' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit 0
packages/cli/tests/case1-accept.test.mjs:26:const PRECHECK = `${HOME}/.local/share/mosaic-stack/copies/${COPY}/scripts/case2-precheck.sh`;
packages/cli/tests/case2-install.test.mjs:2:// scripts/case2-precheck.sh's library checks and --library-status mode. Brief:
packages/cli/tests/case2-install.test.mjs:23:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-install.test.mjs:271:// ---- case2-precheck.sh, the library profile (D6a) ----
packages/cli/tests/case2-install.test.mjs:334:// ---- case2-precheck.sh --library-status (D6b) ----
packages/cli/tests/case2-unit.test.mjs:2:// and scripts/case2-precheck.sh checks the host before they start. Brief: docs/plans/2026-10-20_case2-rows.md
packages/cli/tests/case2-unit.test.mjs:20:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-unit.test.mjs:588:// ---- case2-precheck.sh ----
```
## Run-sheet diff against the pinned base (git diff --unified=0)
```diff
diff --git a/docs/guides/host-run-sheet.md b/docs/guides/host-run-sheet.md
index d2ba28d7c..96fd4f363 100644
--- a/docs/guides/host-run-sheet.md
+++ b/docs/guides/host-run-sheet.md
@@ -209,0 +210,9 @@ sudo -u mosaic -H git clone -q -b refactor /srv/mosaic/incoming/<sha>.bundle /sr
+After a first install, once both units are running (section 10), adopt the installed copy before
+any activate or upgrade. This step runs only on Mos's go, with Jason or Mos running it
+(`scripts/stack-release.sh:14`):
+
+```sh
+sudo -u mosaic -H env PATH=/opt/mosaic-stack/node-<v>/bin:/usr/sbin:/usr/bin:/sbin:/bin \
+ /srv/mosaic/src/mosaic-stack/scripts/stack-release.sh adopt <business> --instance dev --units external
+```
+
@@ -211,0 +221,4 @@ Each upgrade, before `stack-upgrade.sh upgrade`:
+Only the first upgrade after adopt takes `--no-rehearsal`: append it to the upgrade command below.
+Adopt writes no backup, so omitting the flag then refuses `no-backup`. Subsequent upgrades omit it;
+when a backup exists, supplying it refuses `rehearsal-required`.
+
@@ -246,0 +260 @@ step requiring Mos's go (`scripts/stack-copy.sh:3`); the install does not create
+The step runs under sudo's `secure_path`, so it sets its own PATH and umask.
@@ -250,0 +265,2 @@ cd / || exit 1
+umask 077
+PATH=/opt/mosaic-stack/node-<v>/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
```
## Verification
No matching test reads the changed lines, so the stop condition did not apply.
No test run or slot is required under sage-3791. Verification is static: the
required grep and positive control above, exact brief/base pins, scoped diff,
and git diff --check. Reviewer Boyd and Sage own review/landing; no approval
or landing is claimed here.
Receipt correction: the first commit embedded ordinary diff context lines that
contain a single space. git diff --check flagged those as trailing whitespace.
This follow-up uses the exact zero-context diff instead. The run sheet is
unchanged, no tests or Node commands run, and no amend or force-push is used.
## F11 addition (sage-3805, mos-1549, ops-0275)
The brief was extended after F7/F10 drafting and re-pinned at revision3733:
canonical local commit a9bae9af2, same F7 section anchor, full verified blob
0e684ef2103447dcc2013b6bf81b1c4ca5bbc0bd (requested pin0e684ef21034).
Read using git show from the canonical checkout; no fetch, rebase or queue write
is required. This new pin supersedes the earlier brief pin for this follow-up.
Defect: a precheck one second after unit start found no bus.sqlite yet.
Section10 now requires loopback healthz success after a unit start before any
case2-precheck.sh, including section9's --library-status. It uses the same
127.0.0.1:<port>/healthz curl form as scripts/case1-accept.sh:458. The subshell
tries at most30 times, each curl capped at2 seconds, with1 second between failed
attempts (at most89 seconds plus scheduling overhead). Exhaustion prints STOP
and exits only the subshell. The instruction says to stop before precheck/adopt;
it does not exit the interactive operator shell. The section9 library-gitea row
now explicitly requires this bounded wait before --library-status. <port> is the
installed Console unit's --port, not an assumed port or business name.
No host action, test, Node command or credential access occurred.
Required grep repeated for the widened scope:
```sh
git grep -n 'host-run-sheet' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit1, no matches
git grep -n 'case2-precheck.sh' -- 'packages/*/tests/*' 'scripts/test-*.sh'
# exit0, positive control; output follows
packages/cli/tests/case1-accept.test.mjs:26:const PRECHECK = `${HOME}/.local/share/mosaic-stack/copies/${COPY}/scripts/case2-precheck.sh`;
packages/cli/tests/case2-install.test.mjs:2:// scripts/case2-precheck.sh's library checks and --library-status mode. Brief:
packages/cli/tests/case2-install.test.mjs:23:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-install.test.mjs:271:// ---- case2-precheck.sh, the library profile (D6a) ----
packages/cli/tests/case2-install.test.mjs:334:// ---- case2-precheck.sh --library-status (D6b) ----
packages/cli/tests/case2-unit.test.mjs:2:// and scripts/case2-precheck.sh checks the host before they start. Brief: docs/plans/2026-10-20_case2-rows.md
packages/cli/tests/case2-unit.test.mjs:20:const PRECHECK = join(REPO, "scripts", "case2-precheck.sh");
packages/cli/tests/case2-unit.test.mjs:588:// ---- case2-precheck.sh ----
```
No test reads the changed run-sheet lines; the stop condition does not apply.
No test run is needed under the docs-only brief. Static verification checks both
locations, retry limit/timeout, the loopback URL, unchanged F7/F10 and scoped diff.
F11 delta against d9400f0d3 (git diff --unified=0):
```diff
diff --git a/docs/guides/host-run-sheet.md b/docs/guides/host-run-sheet.md
index 96fd4f363..e9fa87f24 100644
--- a/docs/guides/host-run-sheet.md
+++ b/docs/guides/host-run-sheet.md
@@ -322 +322 @@ file with `stat -c '%U %a %n' PATH`, never `cat`. `<business>` is the host's bus
-| `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` | `library-gitea` | 0600 | mosaic | library | `case2-precheck.sh --business <business> --library-status`, after the bus is up: `library-last-error library-credential-missing` until it's placed, then `library <project> last fetched ...` |
+| `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` | `library-gitea` | 0600 | mosaic | library | After a unit start, wait for loopback `/healthz` with the bounded retry in section 10 before `case2-precheck.sh --business <business> --library-status`: `library-last-error library-credential-missing` until it's placed, then `library <project> last fetched ...` |
@@ -357,0 +358,16 @@ None by hand. I2's `case2-unit.sh` enables both units, starts the bus, then the
+After a unit start, wait for the Console's loopback `/healthz` to succeed before running
+`case2-precheck.sh`, including `--library-status` in section 9. Replace `<port>` with the installed
+Console unit's `--port`. This retry is bounded to 30 attempts, each with a 2-second timeout, with
+1 second between failed attempts. If it prints STOP, stop here; do not run the precheck or adopt.
+
+```sh
+(
+ attempts=0
+ until curl -fsS --max-time 2 -o /dev/null 'http://127.0.0.1:<port>/healthz'; do
+ attempts=$((attempts + 1))
+ [ "$attempts" -lt 30 ] || { echo "STOP: loopback healthz did not become ready; do not continue" >&2; exit 1; }
+ sleep 1
+ done
+)
+```
+
```
-9
View File
@@ -1,9 +0,0 @@
# Goldie
Builder seat, Codex gpt-6.1-sol, started by Sage under mos-1542 and mos-1546. Owns F8 and F9, the library persona
import in `scripts/case2-install.sh` (#1818), for the 433 Run B rerun.
No launcher in this directory. The seat runs as a T3 thread and works in its own worktree. It has no credentials of its
own; it pushes only its own `goldie/*` branches through the repository credential helper (mos-0981), never `refactor`,
`main` or another seat's branch. Claude seats orchestrate and review the install path (mos-1542 rule 4). Registered by
Sage on 2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-69
View File
@@ -1,69 +0,0 @@
# Row 322 / #1818 — F8 and F9
Goldie, 2026-10-23. Branch goldie/row322, base 833ec1cc8d7c11842efb82e57c1a0b256c8d22d7.
Brief: docs/plans/2026-10-23_host-fixes-1816-brief.md, F8/F9 section at 7333f94ba9cf.
Issue #1818 is supplied by Sage; no new issue is needed. Reviewer Morgana; Sage owns landing/full gate.
## Implementation and authority
F8 runs the importer from `$COPIES/$COMMIT/scripts/case2-persona-import.mjs`. This uses Node's normal ESM resolution, finding yaml beside the copy and the relative harness module in that same commit. It avoids a custom loader or ambient resolution setting. The copy closure already includes harness/src and business/src, but omitted the importer itself; COPY_PATHS now includes that one script. No new dependency or dependency version change.
F9 selects the first line beginning with an Error type (including TypeError etc.) or ERR_, falling back to the last line for the importer's handled refusal. awk exits after the first matching line and emits exactly one line, keeping the stack and version banner out of the install refusal. Anchoring avoids selecting Node's earlier `throw new ERR_MODULE_NOT_FOUND(...)` source excerpt instead of the actual Error line.
Mos-1550 authorizes the copy-path addition, setup-only additions to world(t), and adding the single new filename to any existing copy-file pin. Existing test edits outside the appended row-322 block:
- case2-install.test.mjs: the fake stack-copy setup gains a loop copying the importer, harness bundle.mjs/tools.mjs, and business vocabulary.mjs; it copies yaml into the fake copy's node_modules. The existing last template-string line is extended to add those setup commands. No existing assertion changes.
- packages/webui/tests/pinned-copy.test.mjs:76: the scripts file-list assertion gains only `case2-persona-import.mjs`, in sorted order. No other change in this test.
The new append-only block builds a scratch clone outside the repository with no node_modules, real installer and importer, stubbed bootstrap/business validation, and a copy with the real importer, relative module closure and yaml. F8 checks installed persona bytes/mode; F9 removes yaml from that copy to produce a real uncaught Node missing-package error, then checks one refusal line carries the cause and no stack/banner. Existing tests missed F8 because the real importer ran from the development checkout, which has yaml installed; the fake copy held only scripts/mosaic. The old fixtures thus never exercised the dependency boundary or an uncaught importer load error.
## Class check and copy-test audit
Before change:
```
grep -n 'node "$REPO/' scripts/case2-install.sh scripts/case2-precheck.sh scripts/stack-copy.sh
scripts/case2-install.sh:185: out="$(node "$REPO/scripts/case2-persona-import.mjs" ...
```
After change: same grep has no hits (exit 1). Direct clone-side .mjs entry points in the three scripts with a bare package import: **scripts/case2-persona-import.mjs → yaml**, moved to the copy by F8. No other direct entry point imports a bare package. Precheck's inline Node programs dynamically import cli/src/config.mjs, business/src/index.mjs, library/src/index.mjs and cli/src/cli.mjs; those entry files import builtins/relative modules only. Library reader.mjs uses its own front-matter parser and has no yaml import. stack-copy sources instance.sh, whose mosaic-config.mjs imports builtins only. Installer bootstrap likewise runs mosaic-config.mjs, and business validation runs business/src/cli.mjs, also no direct bare imports. No other product fix made.
Audit commands (read-only):
```
rg -n 'COPY_PATHS|copy carries|copy.*(hash|sha256|files|file set)|sha256.*copy' packages/*/tests/* scripts/test-*.sh
rg -n 'COPY_PATHS|stack-copy\.sh|readdirSync|sha256|deepEqual' packages/webui/tests/pinned-copy.test.mjs packages/cli/tests/stack-copy.test.mjs
```
Relevant hits: cli/tests/stack-copy.test.mjs:35-47 checks harness file closure (positive control: COPY_PATHS at :36), :52 asserts a required-file subset, :248-264 computes hashes/modes and compares bundle/check-out copy trees dynamically, :274 compares another bundle tree. None freezes a complete path list/hash. webui/tests/pinned-copy.test.mjs:75 pins top-level entries (unchanged), :76 pins scripts' exact file set (approved one-path update). Other readdir hits in those files check cleanup/existing-target behavior, not COPY_PATHS identity. Broad initial grep also hits business/tests/helpers.mjs:33, cli/tests/stack-upgrade.test.mjs:295, webui/tests/views.test.mjs:172, cli/tests/stack-release.test.mjs:1751 and :1987; inspected contexts are unrelated comments/UI copy or different fixture copies. No scripts/test-*.sh hit for these path/hash patterns.
## Verification plan
Pending Sage CLEAR before any test: run new tests on fixed build first, then whole case2-install.test.mjs, whole stack-copy.test.mjs and webui pinned-copy.test.mjs. Demonstrate expected failure at 094124b4e with new block applied (baseline importer path), and kill the `$REPO` reversion mutant. Record all outcomes. First unexpected new-test red is recorded/reported under mos-1272; existing-test edits outside mos-1550 or a second unexpected red stop for Sage.
No tests run yet. No commit/push yet. No host changes, live state, secrets, deploy or release actions.
## Verified results — sage-3808 CLEAR, 2026-10-23T22:47Z
All runs were serialized with `TMPDIR=/mnt/storage/scratch/tmp/goldieuQ2664` (created by `mktemp -d /mnt/storage/scratch/tmp/goldieXXXXXX`), using `timeout -k 10 90` for focused tests and `timeout -k 10 180` for whole files/negative-proof driver. Node v26.8.1. Dependencies were read through the ignored, uncommitted node_modules symlink approved by sage-3804; each scratch clone had no node_modules. No /tmp fixture was used.
| Run | Result | Evidence |
| --- | --- | --- |
| New tests, first run | 2/2 pass, exit 0 | test-new.log |
| Whole case2-install.test.mjs | 26/26 pass, exit 0 | test-install.log |
| Whole stack-copy.test.mjs | 18/18 pass, exit 0 | test-copy.log |
| Whole webui pinned-copy.test.mjs | 10/10 pass, exit 0 | test-pinned-copy.log |
| Installer from 094124b4e, new tests | 0/2 pass, exit 1, expected red | baseline-094124b4e.log |
| F8 mutant: restore `$REPO` importer path only | 0/1 pass, exit 1, killed | mutant-F8.log |
| F9 mutant: restore tail-only refusal only | 0/1 pass, exit 1, killed | mutant-F9.log |
| Fixed installer restored, new tests rerun | 2/2 pass, exit 0 | test-restored.log |
The baseline proof temporarily installs the exact installer bytes from `git show 094124b4e:scripts/case2-install.sh`, retaining the newly appended tests and approved fixture setup. Relevant installer/importer/copy/test source paths have no changes between N and the branch base 833ec1cc8 (`git diff 094124b4e 833ec1cc8 --` those paths is empty). It is a targeted baseline proof, not a full checkout/suite run at N. Baseline failures show `persona-import: Node.js v26.8.1`; the F8-only mutant exposes the underlying `Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml'` from the scratch clone. F9-only mutant again prints the banner. All expected failures are deliberate acceptance checks; no unexpected first-run red occurred.
negative-proof.py records each run, checks the expected failure count/exit, restores in a finally block, and verifies fixed installer bytes after each run. Restored SHA-256 each time: `c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462`. Final focused green confirms restoration. git diff --check passed.
`git diff --numstat -- packages/cli/tests/case2-install.test.mjs`: **57 additions, 1 deletion**. Outside the new appended block, original line 70's template-string close moves after four new setup lines: the existing mkdir/empty mosaic/echo command is unchanged, followed by the `for file ...` line, mkdir/cp loop body, `done`, and mkdir/cp yaml line. All pre-existing assertions remain byte-for-byte unchanged. The only pre-existing assertion edit anywhere is the expressly approved one-filename addition at packages/webui/tests/pinned-copy.test.mjs:76.
Sage's full package/script gate and Morgana's review remain pending. This builder ran only the approved three whole test files and targeted acceptance checks. Sage retains queue transition/review posting and integration authority; no PR was created by Goldie.
Evidence log lines have trailing whitespace removed for git diff --check; test content and outcomes are unchanged.
@@ -1,4 +0,0 @@
c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462 scripts/case2-install.sh
cb1a4fef997b9696406e324878d7a0e5181892622162c9ce77a8fd625b5cd59a scripts/stack-copy.sh
a7b02af4b5df92e6d727fe8c67565ea0483d6d57531765dee68f54fd97e7c23f packages/cli/tests/case2-install.test.mjs
2574b414459f4cfb187de64b9a2fbe34ab4b4d64f582caae8baf6a1985e8cd3c packages/webui/tests/pinned-copy.test.mjs
@@ -1,70 +0,0 @@
TAP version 13
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
not ok 1 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 90.885888
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:451:1'
failureType: 'testCodeFailure'
error: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-rQM4ct/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-rQM4ct/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:454:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
not ok 2 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 91.239243
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:462:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/. Input:
'case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: refused: persona-import: Node.js v26.8.1\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-O6VWs2/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
operator: 'match'
stack: |-
refused (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:137:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:469:3)
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)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..2
# tests 2
# suites 0
# pass 0
# fail 2
# cancelled 0
# skipped 0
# todo 0
# duration_ms 287.462723
@@ -1,37 +0,0 @@
TAP version 13
# Subtest: row 322 F8: library personas install from the copy with no clone node_modules
not ok 1 - row 322 F8: library personas install from the copy with no clone node_modules
---
duration_ms: 90.468116
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:451:1'
failureType: 'testCodeFailure'
error: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml' imported from /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-bzVkal/clone/scripts/case2-persona-import.mjs
1 !== 0
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: 0
actual: 1
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:454:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 194.231229
@@ -1,41 +0,0 @@
TAP version 13
# Subtest: row 322 F9: importer refusal keeps the error line rather than the Node banner
not ok 1 - row 322 F9: importer refusal keeps the error line rather than the Node banner
---
duration_ms: 92.814376
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:462:1'
failureType: 'testCodeFailure'
error: |-
The input did not match the regular expression /persona-import: Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package 'yaml'/. Input:
'case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.config/mosaic-dev/config.json\n' +
'case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc\n' +
'case2-install: refused: persona-import: Node.js v26.8.1\n'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
actual: |-
case2-install: config /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.config/mosaic-dev/config.json
case2-install: copy /mnt/storage/scratch/tmp/goldieuQ2664/case2-install-6HcR0m/home/.local/share/mosaic-stack/copies/cccccccccccccccccccccccccccccccccccccccc
case2-install: refused: persona-import: Node.js v26.8.1
operator: 'match'
stack: |-
refused (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:137:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row322/packages/cli/tests/case2-install.test.mjs:469:3)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 200.516406
@@ -1,6 +0,0 @@
baseline-094124b4e: exit 1, expected failures 2
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
mutant-F8: exit 1, expected failures 1
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
mutant-F9: exit 1, expected failures 1
restored sha256 c23a08324528cc29895014093c661948a5c8eb40316c7b2e21c6f833cf5d6462
@@ -1,39 +0,0 @@
"""Run row-322 expected-red proofs, always restoring installer bytes."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
installer = root / 'scripts/case2-install.sh'
logs = Path(__file__).resolve().parent
fixed = installer.read_bytes()
baseline = subprocess.check_output(['git', 'show', '094124b4e:scripts/case2-install.sh'], cwd=root)
f8 = fixed.replace(b'$COPIES/$COMMIT/scripts/case2-persona-import.mjs', b'$REPO/scripts/case2-persona-import.mjs')
assert f8 != fixed
old_error = baseline.splitlines()[185]
new_error = fixed.splitlines()[185]
f9 = fixed.replace(new_error, old_error)
assert f9 != fixed
try:
for label, source, pattern, failures in [
('baseline-094124b4e', baseline, 'row 322', 2),
('mutant-F8', f8, 'row 322 F8', 1),
('mutant-F9', f9, 'row 322 F9', 1),
]:
installer.write_bytes(source)
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
f'--test-name-pattern={pattern}', 'packages/cli/tests/case2-install.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(logs / f'{label}.log').write_text(result.stdout)
print(f'{label}: exit {result.returncode}, expected failures {failures}', flush=True)
assert result.returncode == 1, result.stdout
assert f'# fail {failures}\n' in result.stdout, result.stdout
assert 'ERR_MODULE_NOT_FOUND' in result.stdout, result.stdout
installer.write_bytes(fixed)
assert installer.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
finally:
installer.write_bytes(fixed)
assert installer.read_bytes() == fixed
@@ -1,26 +0,0 @@
✔ a copy carries every file the harness modules read from it (891.310838ms)
✔ shared copy: npm ci, pushed reachability, readonly/existing copy, webui delegation (537.82282ms)
✔ wrapper preserves Q81 lease and marker, rejects foreign/symlink; check uses effective roots (713.621566ms)
✔ row 190 N6: check --instance passes only when the wrapper exports the instance's MOSAIC_CONFIG (657.082198ms)
✔ bus unit copy/pm pins, install refuses all busy states and failed reads, drop-in refusal (799.380065ms)
✔ missing package manifest refuses before npm; npm failure removes the unpublished temp copy (499.872009ms)
✔ real pinned install loads bus and Console; same-copy mosaic talk passes proof, other trees refuse (3824.368055ms)
✔ bus status reads effective copy/pm/drop-ins without writes; check refuses an overridden ExecStart (668.500662ms)
✔ drop-ins in either override field and malformed metadata refuse; uninstall also checks --no-reload (399.490955ms)
✔ install never shows a template name on systemd 261; template drop-ins still refuse (318.110324ms)
✔ unlisted instance drop-in is found in another unit lookup directory; path discovery errors refuse (347.788225ms)
✔ inherited mosaic-.service.d override refuses without changing the unit (403.142763ms)
✔ inherited service.d override refuses without changing the unit (384.337811ms)
✔ inherited drop-in unreadable directory or file refuses before writing (370.630432ms)
✔ row 232: copy --bundle copies a SHA the bundle's refactor reaches, byte-identical to copy SHA, and refuses the rest (2257.394077ms)
✔ row 232: wrapper --create writes the first wrapper only, the same bytes wrapper ID writes, and refuses over any file (779.276348ms)
✔ row 232: check --units external reads the system units, the default reads the user units (641.792634ms)
✔ Vikunja 614: bundle and checkout copy leave an unreadable caller directory before find (1047.143772ms)
ℹ tests 18
ℹ suites 0
ℹ pass 18
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 15638.417631
@@ -1,34 +0,0 @@
✔ the library install writes the business file, personas and slot dir, makes the copy and starts nothing (258.707115ms)
✔ a library re-run passes and leaves every file as it was (391.278216ms)
✔ a re-run refuses business-file-differs and leaves the file unchanged (274.686097ms)
✔ a re-run refuses persona-differs when an installed persona isn't the staged one, and writes nothing (274.084555ms)
✔ the factory install writes no library mapping, persona or slot dir (175.791904ms)
✔ the factory install refuses a library project and bus.tester off, before anything is written (72.301025ms)
✔ the library install refuses bus.tester on and a persona var in the input (76.469642ms)
✔ the install refuses root, a business id mismatch, another --home and a config dir with group bits (57.89739ms)
✔ the library install needs --personas (5.495139ms)
✔ a validate refusal removes the business file this run wrote (230.311754ms)
✔ N4: a validate refusal on a re-run leaves the matching business file in place, byte for byte (397.123924ms)
✔ a clean library host passes the precheck with no mirror yet (404.408689ms)
✔ the library precheck refuses a persona file at 0644 (399.17119ms)
✔ the library precheck refuses a persona whose digest isn't persona.sha256 (402.47159ms)
✔ the library precheck refuses a persona dir with group bits (390.108413ms)
✔ the library precheck refuses a business file at 0644 (402.829663ms)
✔ the library precheck refuses a changes.jsonl at 0644 (573.861228ms)
✔ the library precheck refuses a Discord binding (386.405794ms)
✔ --library-status refuses library-mirror-missing before the bus has made the mirror (309.213305ms)
✔ --library-status refuses library-never-synced on a fresh mirror (330.922116ms)
✔ --library-status names a recorded lastError before the null lastFetch it leaves (N3) (335.576032ms)
✔ --library-status prints each R-problem's path as an escaped JSON string, then the count, and passes (379.69927ms)
✔ --library-status takes no --profile (5.544173ms)
✔ Vikunja 614: install preserves the explicit notifier decision bytes and mode and creates none when absent (465.015156ms)
✔ row 322 F8: library personas install from the copy with no clone node_modules (139.662489ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (95.452368ms)
ℹ tests 26
ℹ suites 0
ℹ pass 26
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 7340.739711
-10
View File
@@ -1,10 +0,0 @@
✔ row 322 F8: library personas install from the copy with no clone node_modules (178.794273ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (101.739988ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 394.59041
@@ -1,18 +0,0 @@
✔ copy writes the Console paths at the full id, read-only, and leaves an existing copy alone (872.104795ms)
✔ copy refuses a commit off refactor, an unknown one, a worktree and a bad copies directory (1245.376299ms)
✔ copy writes nothing when an archived path is missing at the commit (409.033887ms)
✔ a copy that loses the rename race leaves no read-only temp directory behind (548.743561ms)
✔ a copy whose rename fails with no target refuses, writes no copy and leaves no temp directory (534.468073ms)
✔ a copy that meets a target holding files says exists: and moves nothing into it (541.303916ms)
✔ a copy that meets an empty target directory replaces it by rename, not by moving into it (547.966283ms)
✔ serve runs from a copy: /healthz 200, the config check, roles and RELEASE from the copy, the queue from --checkout (856.865742ms)
✔ serve refuses a --checkout that is not an absolute directory path (274.552284ms)
✔ consoleReads runs the queue read under queueRoot and reads RELEASE under root (49.311324ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6003.825092
@@ -1,10 +0,0 @@
✔ row 322 F8: library personas install from the copy with no clone node_modules (148.410784ms)
✔ row 322 F9: importer refusal keeps the error line rather than the Node banner (94.205865ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 344.990842
-141
View File
@@ -1,141 +0,0 @@
# Row 323 / #1819 — Case 1 factory business file
Goldie, 2026-10-23. New isolated worktree `/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323`, branch `goldie/row323`, base local refactor `da537e7d159be0bf39a36eaaa65cd16c20373b72` (descends from brief commit 68f3bded5). Row322 worktree remains unchanged.
## Sources and input boundary
Read brief docs/plans/2026-10-23_case1-business-file-brief.md, row195 DESIGN.md (host values sections1/2/6/7), installer factory HELPER, precheck profile checks, case1-accept F3/F7, business loader and credentials/launch checks, role definitions and business fixture helpers. No host access or credential read.
DESIGN fixes business `mosaic-stack`, OS user mosaic, home `/srv/mosaic`, config `/srv/mosaic/.config/mosaic-dev`, data `/srv/mosaic/.mosaic-dev`, Claude only, tester on. Human id is explicitly left to the go (:82/:300), no project root is named. Business loader requires at least one project/root (:261-275), tracker.sync bot/botId/Vikunja credential reference (:85-94), launch.models and nonempty launch.instances excluding launcher (:186-193). These host/business values are not supplied by DESIGN. Per sage-3815 and brief, stop configuration rather than guess. Asked Sage in goldie-0008 for exact values.
Recommended minimal role set: chief-of-staff instance chief with claude-code harness plus credential-free assistant solely because launch.instances must contain a declared non-launcher. Both arbiters may reference chief; no additional PM/CTO role credential required. This remains a recommendation pending input, not a written host config.
Literal top-level `library` is ignored by the installer plan and rejected later by business validation as unsupported key; a library project is refused by the factory plan as profile-library-business. Asked Sage which intended refusal assertion to use; recommend covering both through their real paths without a product change.
## Authority and status
Sage-3815 explicitly authorizes claimant queue moves despite brief's generic no-queue-writes boundary. Claim op goldie-0323-start-1 retries every20s on lock contention per sage-3814; no lock touched. Tests and mutant require new Sage CLEAR; none run. No business JSON or test written yet. No commit/push yet. BUILD/SESSIONS/BUILD-LOG notes record investigation only.
## Rulings and static blocker, 23:02Z
Sage-3820 accepts chief + credential-free assistant because launch.instances must be nonempty and exclude chief, both arbiters chief. It accepts literal library-key rejection through final real business validation and library-project rejection through factory planning, no production change. Sage-3822 supplies project mosaic-stack at /srv/mosaic/src/mosaic-stack and model claude-opus-5-5 (provider/thinking to be sourced). Sage-3823 relays Mos-1554: human jason, no real tracker bot, token path must be absent and never created; stop immediately if this refuses boot/launch.
Static trace proves refusal, reported to Sage in goldie-0009:
- cli/src/config.mjs:127 loads business. trackerFor :60 returns null without tracker.baseUrl; :137-138 suppresses only config.trackers, not business tracker.sync.
- bus/src/runtime.mjs:131 registers business tracker.sync credentials under business/@sync; :157 constructs Credentials regardless of config.trackers.
- bus/src/credentials.mjs:134-139 eagerly reads every reference. Ordinary ref.file lstat/realpath failure at :225-228 throws credential-file during boot, before chief can launch.
- The business credential check warns for unset env, but bus/src/credentials.mjs:268 sends undefined to #format; :278 rejects credential-format. An env reference is not a bypass and is outside the brief's file requirement.
- Install refuses even earlier: business/src/cli.mjs:84-88 checks tracker.sync references, business/src/credentials.mjs:78-83 records a missing ordinary file as a problem, CLI :95-97 exits2. case2-install.sh:217-220 wraps validate refusal and removes the business file it created.
- business/src/business.mjs:41 requires tracker bot names to begin bot-, :42 requires positive botId. Mos's illustrative no-bot-mosaic-stack-sync would fail that shape; a bot-no-bot marker still cannot solve the absent-file refusal.
Status: real fail-closed product boundary. No business JSON, test, placeholder credential, host action or tests written/run. Await Sage/Mos resolution; do not bypass by inventing a token or expanding product scope. Only investigation notes exist in this worktree. Claim attempts continue separately under the queue window's explicit retry instruction.
## Resumed after Q247 landing — sage-3859
Current base is pushed `dd6cb5872b972bcabb99b82589b2905e4e47dd3e`. Preserved earlier investigation as a notes-only commit, then rebased onto that tip; only append-only BUILD-LOG/SESSIONS conflicts, resolved retaining the entire landed files and appending this row's original suffix. Earlier equivalent local refactor doc commits were skipped by git as already applied. No other row323 source existed before this resume.
Q247 removes the static blocker: this file now omits tracker entirely, with no dummy bot/token/reference. No tracker credential or token file is required or created. The separate Claude session login file remains the operator's run-sheet step, as described in DESIGN section3; this document contains no credential value or reference, and this work reads no credential.
Approved values and minimal role set:
- business mosaic-stack, factory bus.tester true, no library key/project (DESIGN sections1/2/7).
- human jason (Mos-1554, sage-3823); project mosaic-stack at /srv/mosaic/src/mosaic-stack (sage-3822).
- chief-of-staff chief and credential-free assistant, both arbiters chief, launch.by chief, launch.instances [assistant] (sage-3820). The loader requires nonempty launch.instances excluding the launcher (business.mjs:186-191), so a chief-only document cannot load. No other role is needed.
- Both roles use claude-code; plugins.harness enables only claude-code. No pi route or fallback.
- chief model claude-opus-5-5 is Sage's explicit choice (sage-3822). Provider anthropic/model claude-opus-5-5 is the repository model mapping at packages/business/README.md:185. There is no separate per-model default thinking catalog in the implementation: choose high from the Claude harness's supported LEVELS at packages/plugins/harness/claude-code/index.mjs:28, following the existing chief route convention at packages/cli/tests/session-launch.test.mjs:83. Both roles use that one declared model/effort; no extra model or capacity policy is introduced.
One new file packages/business/tests/case1-business-file.test.mjs has seven cases. It first loads the shipped JSON bytes unchanged from a temporary business-file location (loader checks shape, no host path access). Its runtime/CLI/install cases copy the document into a scratch input and replace only the project root with a scratch fixture root, per sage-3820's instruction to use test fixture paths. The real factory installer takes that input via --business-file and uses its real bootstrap/business validation, with only stack-copy stubbed (no clone/install/network). The boot case uses bootConfig + actual startBroker and asserts no tracker config or credential status. Negative cases cover bus.tester false (bus-tester-off), literal library (final validate unsupported key, per sage-3820), and a library project (factory plan profile-library-business). No pre-existing test changed.
The new tester-switch mutant flips only bus.tester in the shipped JSON, targets the new exact-document assertion, then restores/checks bytes in finally. Prepared but not run. Tests and mutant remain held until Sage CLEAR; only syntax/diff/static JSON checks have run. Row323 claimant is still in-progress from rev3749.
## Field provenance and pre-test commit — sage-3861
Sage-3861 CLEAR explicitly requires committing the draft before any run, so test counts attach to a commit. This is a specific exception to the usual green-before-commit rule. Until recorded below, this commit is untested. No old test is modified.
| Document field | Value and source |
| --- | --- |
| businessVersion | 1, business loader's version requirement (business/src/business.mjs:233). Schema value, not a mos-1554 host choice. |
| id | mosaic-stack, row195 DESIGN section7 (sage-1813), restated by row323 brief. |
| human | jason, mos-1554 relayed by sage-3823. |
| arbiters.delivery / arbiters.technical | chief / chief, sage-3820's explicit minimal-role ruling; both reference the chief-of-staff definition. |
| projects.mosaic-stack.root | /srv/mosaic/src/mosaic-stack, sage-3822's explicit project-id/root decision. DESIGN supplied /srv/mosaic as home but did not supply this root. |
| vars.bus.tester | true, DESIGN section2 factory profile and installer bus-tester-off requirement. |
| plugins.harness | [claude-code], brief's Claude-only/P19 mos-0865 boundary and DESIGN section3. |
| roles.chief.definition | chief-of-staff, brief/roles/chief-of-staff.json; no credential or tracker bot. |
| roles.assistant.definition | assistant, sage-3820 acceptance of this one extra credential-free role, required because launch.instances must be nonempty and exclude chief. |
| roles.*.vars.harness | claude-code for both; same Claude-only rule, no fallback route. |
| roles.*.vars.model | claude-opus-5-5 for both, chief model explicitly chosen in sage-3822; reuse the single declared model for assistant to avoid adding another model decision. |
| roles.*.vars.thinking | high, supported effort in packages/plugins/harness/claude-code/index.mjs:28; existing chief convention packages/cli/tests/session-launch.test.mjs:83. The repository has no per-model default-effort catalog; this is an explicit supported-convention choice, not an invented catalog default. |
| launch.by | chief, brief and F7's requirement to name the chief launch; sage-3820. |
| launch.instances | [assistant], sage-3820 and business.mjs:186-191's nonempty/non-launcher rule. |
| launch.models.claude-opus-5-5.provider / model | anthropic / claude-opus-5-5, repository model mapping packages/business/README.md:185 and sage-3822. The model id is also the map key so no extra alias is introduced. |
| Omitted tracker / credentials / library | Q247/mos-1555 and sage-3859 supersede mos-1554's proposed absent-token placeholder; no bot, token path, credential value or library project/key is present. |
Only human=jason is a literal field value supplied by mos-1554. The no-bot intent also comes from mos-1554; Q247 implements it by omission. Other fields deliberately cite the actual design/schema/Sage source rather than attributing them to mos-1554.
Planned runs under sage-3861: new file whole; business suite whole; CLI suite whole; tester-switch mutant; restored new file whole. Fresh scratch TMPDIR and timeout-k10 for each, one process at a time. Existing-test red or any additional old-test edit stops work; first-run new red is recorded and reported. Push remains own branch goldie/row323 after the test sequence/report evidence is concrete.
## First slot result — stopped on existing CLI test red
Committed draft before any test: `571376c30e77f46bbe41069633390cc001f57ac8` (base dd6cb5872b972bcabb99b82589b2905e4e47dd3e), per sage-3861. Fresh TMPDIR `/mnt/storage/scratch/tmp/goldielKvBJv`, allocated by mktemp under the mandated scratch parent. All runs serialized with timeout -k 10. Source and test bytes remained unchanged throughout.
| Run | Command after TMPDIR/timeout | Outcome |
| --- | --- | --- |
| New whole file | timeout -k 10 180 node --test --test-reporter=tap packages/business/tests/case1-business-file.test.mjs | 7/7 pass, 0 fail/skip/cancel; test-new.log |
| Whole business suite | timeout -k 10 600 node --test --test-reporter=tap --test-concurrency=1 packages/business/tests/ | 261/261 pass, 0 fail/skip/cancel; test-business-suite.log |
| Whole CLI suite | timeout -k 10 1200 node --test --test-reporter=tap --test-concurrency=1 packages/cli/tests/ | Interrupted on existing-test failure; 631 top-level passes/1 failure recorded before termination, no final full-suite totals; test-cli-suite.log |
| Tester-switch mutant | Not run | Stop rule reached first |
| Restored new file | Not run | Mutant not run; source already matches manifest |
Existing failure: CLI test579, packages/cli/tests/revoke-window.test.mjs:170: `row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22`. After 20111.683685ms it failed `no RESULT on 61c275d4-42f9-4a92-8a43-1ae94c25b730`, ask assertion at167, Promise.all index1, test at194. This file has no changes from base. No cause or flakiness claim is established.
On observing the red in the suite log, sent SIGTERM to this run's verified timeout process; the suite ended exit1 with `Interrupted while running: packages/cli/tests/stack-release-external.test.mjs` (it had progressed while output was being inspected). One remaining process was positively identified by an argument under this exact scratch TMPDIR and cleaned with SIGTERM; no live host process/state touched. No further tests or reruns, no old test changes.
Reported immediately to Sage as goldie-0016, returned slot, requested the bounded investigation/retry ruling. JSON/new-test manifest still verifies. Candidate is not verified/ready for review and has not been pushed as such. These failure notes/logs remain local evidence until Sage's ruling; no mutant acceptance claim.
## Mos-1560 isolated rerun — second red, stopped
Sage-3864 CLEAR authorized three whole-file revoke-window runs on unchanged571376c30, followed only on three passes by CLI/mutant/restored runs. Every run had to begin with date -u, uptime and the last line of sage-busy-now.sh. Only run1 occurred; it failed, reaching the second-red stop condition.
Log: revoke-window-run-1.log. Exact start/load lines:
```
Sat Oct 24 00:24:22 UTC 2026
19:24:22 up 10 days, 10:44, 2 users, load average: 1.46, 1.68, 2.70
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/goldieBObDQ9
```
Command: `TMPDIR=<fresh mktemp above> timeout -k 10 180 node --test --test-reporter=tap packages/cli/tests/revoke-window.test.mjs`.
Whole file result: 0/1 pass, 1 failure, exit1, no skips/cancellations. Same unchanged row306 R4 test at revoke-window.test.mjs:170 failed after20126.967034ms: `no RESULT on dc4ccc94-4d08-4da1-bc66-2f4fcfc098dc`, ask167, Promise.all index1, test194. No edits, no run2/3, no full CLI rerun, no mutant/restored run. No remaining process with this exact run's scratch path was found after the file finished. Manifest still matches. This proves repeat failure under the recorded CLEAR/load conditions, not a root cause.
Reported to Sage as goldie-0018 with full log path, time/load/busy lines, test and error. Slot returned; holding for root-cause assignment/ruling on base dd6cb5872. Remote goldie/row323 stays the unverified draft571376c30; post-run failure evidence remains local. All row323 product/test sources are still exactly those committed before the first slot.
## Root cause correction and accepted evidence — mos-1561, sage-3869
The repeated revoke-window red was caused by TMPDIR length, not Codex. Sage identified and reproduced it (sage-3869; recorded in their local dda2d07b4): newRun rejects a tools.sock path exceeding107 bytes at packages/seat/src/session.mjs:278. The 37-byte `/mnt/storage/scratch/tmp/goldieXXXXXX` combined with revoke-window's fixture root produces109 bytes, while Sage's35-byte prefix fits107. The test drops the launcher refusal log, so the symptom was a20s no-RESULT wait. The detached base dd6cb5872 also failed0/1 after20106.869486ms with long TMPDIR (log copied as revoke-window-detached-base.log). Prior messages attributing the difference to an unspecified environment are superseded by this diagnosis; no product/old-test fix is needed for row323.
From now on every suite uses `export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)` (31bytes). Under sage-3869 CLEAR, all source/test bytes remained exactly571376c30; each run got its own fresh31-byte TMPDIR and began with date-u, uptime, and busy-helper's last line:
| Run / log | UTC start | Load averages | Busy | Result |
| --- | --- | --- | --- | --- |
| revoke-window-short-tmp.log | 2026-10-24 00:36:41 | 1.01,1.34,1.91 | CLEAR | Whole file1/1 pass; test703.937594ms, file838.51754ms; TMPDIR lCsnmg |
| test-business-short-tmp.log | 2026-10-24 00:36:50 | 1.08,1.35,1.91 | CLEAR | Whole suite261/261 pass, 6415.261266ms; TMPDIR flQGnB |
| test-new-short-tmp.log | 2026-10-24 00:37:03 | 1.37,1.40,1.92 | CLEAR | Whole new file7/7 pass, 611.516605ms; TMPDIR HNhoti |
| mutant-driver.log + mutant.log | 2026-10-24 00:37:12 | 1.38,1.41,1.92 | CLEAR | Mutant killed: targeted exact-document test0/1, expected exit1; driver exit0; TMPDIR XAmmxp |
| test-new-restored.log | 2026-10-24 00:37:25 | 1.61,1.46,1.93 | CLEAR | Restored new file7/7 pass, 616.699665ms; TMPDIR IVLOcQ |
All TMPDIRs above are under /mnt/storage/scratch/tmp/. Every run used timeout-k10 (180s limit, business suite600s), one run at a time. Positive runs have0 failures/skips/cancellations. These are fresh accepted row323 results on committed571376c30. No full CLI rerun was authorized or performed: under mos-1561, Sage's landing gate supplies the CLI suite acceptance/counts, while Goldie's evidence is the business suite, new file and mutant. The earlier partial CLI log remains diagnostic evidence, not a passing-suite claim.
Required tester-switch mutant:
```diff
- "bus.tester": true
+ "bus.tester": false
```
It fails the new committed-document assertion at case1-business-file.test.mjs:66 (`false !== true`). mutant.py restores exact original bytes in finally; restored JSON SHA256 `85e6b9ac62785734fb9d854ea70ea550e171b5060b5cad0417e2f42f0b60c59e`. Both manifest entries verify after restoration, and the whole new file passes again. No pre-existing test or production source changed. git diff --check passes.
Committing these notes and preserved logs is evidence-only on top of the tested571376c30. The product manifest remains the same two paths/digests. Duckworth's static review and Sage's integration/full gate remain their authority. Logs have trailing whitespace removed for git diff --check; substantive output/results are unchanged. Earlier stopped runs remain recorded honestly above and in their logs.
@@ -1,2 +0,0 @@
85e6b9ac62785734fb9d854ea70ea550e171b5060b5cad0417e2f42f0b60c59e docs/guides/case1-business.mosaic-stack.json
aecd1c0f0fe1eb46800a7dea6f1f2f6a965f1daa33a39f9837bb1e2c19e0abfc packages/business/tests/case1-business-file.test.mjs
@@ -1,6 +0,0 @@
Sat Oct 24 00:37:12 UTC 2026
19:37:12 up 10 days, 10:57, 2 users, load average: 1.38, 1.41, 1.92
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/XAmmxp bytes=31
bus.tester mutant: exit 1
restored sha256 85e6b9ac62785734fb9d854ea70ea550e171b5060b5cad0417e2f42f0b60c59e
-34
View File
@@ -1,34 +0,0 @@
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
not ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 4.020549
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/business/tests/case1-business-file.test.mjs:57:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly equal:
false !== true
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
actual: false
operator: 'strictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/business/tests/case1-business-file.test.mjs:66:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 113.96636
-26
View File
@@ -1,26 +0,0 @@
"""Row 323 tester-switch mutant; run only after Sage CLEAR."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
source = root / 'docs/guides/case1-business.mosaic-stack.json'
fixed = source.read_bytes()
needle = b'"bus.tester": true'
assert fixed.count(needle) == 1
try:
source.write_bytes(fixed.replace(needle, b'"bus.tester": false'))
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
'--test-name-pattern=row 323: committed Case 1 document loads',
'packages/business/tests/case1-business-file.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Path(__file__).with_suffix('.log').write_text(result.stdout)
print(f'bus.tester mutant: exit {result.returncode}', flush=True)
assert result.returncode == 1, result.stdout
assert '# fail 1\n' in result.stdout and 'false !== true' in result.stdout, result.stdout
finally:
source.write_bytes(fixed)
assert source.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
@@ -1,39 +0,0 @@
Sat Oct 24 00:29:57 UTC 2026
19:29:57 up 10 days, 10:49, 2 users, load average: 1.29, 1.30, 2.21
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/goldieccUVab
dd6cb5872b972bcabb99b82589b2905e4e47dd3e
v26.8.1
/usr/bin/node
TAP version 13
# Subtest: row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
not ok 1 - row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
---
duration_ms: 20106.869486
type: 'test'
location: '/mnt/storage/scratch/tmp/goldie-base2qHbGq/worktree/packages/cli/tests/revoke-window.test.mjs:170:1'
failureType: 'testCodeFailure'
error: 'no RESULT on 94ddbce3-5e46-49a7-8f63-60af8995c10d'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
operator: '=='
stack: |-
ask (file:///mnt/storage/scratch/tmp/goldie-base2qHbGq/worktree/packages/cli/tests/revoke-window.test.mjs:167:10)
runNextTicks (node:internal/process/task_queues:65:5)
listOnTimeout (node:internal/timers:644:9)
process.processTimers (node:internal/timers:618:7)
async Promise.all (index 1)
async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/goldie-base2qHbGq/worktree/packages/cli/tests/revoke-window.test.mjs:194:3)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 20235.90436
@@ -1,33 +0,0 @@
Sat Oct 24 00:24:22 UTC 2026
19:24:22 up 10 days, 10:44, 2 users, load average: 1.46, 1.68, 2.70
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/goldieBObDQ9
TAP version 13
# Subtest: row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
not ok 1 - row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
---
duration_ms: 20126.967034
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/cli/tests/revoke-window.test.mjs:170:1'
failureType: 'testCodeFailure'
error: 'no RESULT on dc4ccc94-4d08-4da1-bc66-2f4fcfc098dc'
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected: true
operator: '=='
stack: |-
ask (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/cli/tests/revoke-window.test.mjs:167:10)
async Promise.all (index 1)
async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row323/packages/cli/tests/revoke-window.test.mjs:194:3)
async Test.run (node:internal/test_runner/test:1409:7)
async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 20266.295192
@@ -1,22 +0,0 @@
Sat Oct 24 00:36:41 UTC 2026
19:36:41 up 10 days, 10:56, 2 users, load average: 1.01, 1.34, 1.91
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/lCsnmg bytes=31
TAP version 13
# usage: {"event":"usage-unknown","business":"acme","run":"r73cb69bf7381","instance":"assistant","turn":"7b64df9d-f571-4f43-a678-dbd5ce7714a9","source":"@none","code":"baseline-unknown"}
# usage: {"event":"usage-unknown","business":"acme","run":"r89b03e6f1158","instance":"assistant","turn":"aaa36964-0a78-4ba1-9442-d6f0aa35f18e","source":"@none","code":"baseline-unknown"}
# Subtest: row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
ok 1 - row 306 REQ-LAUNCH-1 R4 a revoked run whose call lands before the SIGTERM ends stopped/0; a run the human stopped in the same window still ends session-invalid/22
---
duration_ms: 703.937594
type: 'test'
...
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 838.51754
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,56 +0,0 @@
Sat Oct 24 00:37:25 UTC 2026
19:37:25 up 10 days, 10:57, 2 users, load average: 1.61, 1.46, 1.93
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/IVLOcQ bytes=31
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 3.816879
type: 'test'
...
# Subtest: row 323: real business validate accepts the Case 1 fixture without tracker credentials
ok 2 - row 323: real business validate accepts the Case 1 fixture without tracker credentials
---
duration_ms: 70.395579
type: 'test'
...
# Subtest: row 323: real factory install accepts the document's scratch input and writes it unchanged
ok 3 - row 323: real factory install accepts the document's scratch input and writes it unchanged
---
duration_ms: 164.092704
type: 'test'
...
# Subtest: row 323: the Case 1 business boots without a tracker or @sync credential
ok 4 - row 323: the Case 1 business boots without a tracker or @sync credential
---
duration_ms: 48.455987
type: 'test'
...
# Subtest: row 323: factory install refuses bus.tester false before writing
ok 5 - row 323: factory install refuses bus.tester false before writing
---
duration_ms: 32.684801
type: 'test'
...
# Subtest: row 323: literal library key refuses through final business validation
ok 6 - row 323: literal library key refuses through final business validation
---
duration_ms: 147.619094
type: 'test'
...
# Subtest: row 323: a library project refuses through the factory profile plan
ok 7 - row 323: a library project refuses through the factory profile plan
---
duration_ms: 37.280986
type: 'test'
...
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 616.699665
@@ -1,56 +0,0 @@
Sat Oct 24 00:37:03 UTC 2026
19:37:03 up 10 days, 10:56, 2 users, load average: 1.37, 1.40, 1.92
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/HNhoti bytes=31
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 4.019742
type: 'test'
...
# Subtest: row 323: real business validate accepts the Case 1 fixture without tracker credentials
ok 2 - row 323: real business validate accepts the Case 1 fixture without tracker credentials
---
duration_ms: 71.809045
type: 'test'
...
# Subtest: row 323: real factory install accepts the document's scratch input and writes it unchanged
ok 3 - row 323: real factory install accepts the document's scratch input and writes it unchanged
---
duration_ms: 158.271252
type: 'test'
...
# Subtest: row 323: the Case 1 business boots without a tracker or @sync credential
ok 4 - row 323: the Case 1 business boots without a tracker or @sync credential
---
duration_ms: 47.408675
type: 'test'
...
# Subtest: row 323: factory install refuses bus.tester false before writing
ok 5 - row 323: factory install refuses bus.tester false before writing
---
duration_ms: 33.173372
type: 'test'
...
# Subtest: row 323: literal library key refuses through final business validation
ok 6 - row 323: literal library key refuses through final business validation
---
duration_ms: 157.331263
type: 'test'
...
# Subtest: row 323: a library project refuses through the factory profile plan
ok 7 - row 323: a library project refuses through the factory profile plan
---
duration_ms: 31.660009
type: 'test'
...
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 611.516605
-52
View File
@@ -1,52 +0,0 @@
TAP version 13
# Subtest: row 323: committed Case 1 document loads unchanged through the business loader
ok 1 - row 323: committed Case 1 document loads unchanged through the business loader
---
duration_ms: 4.475258
type: 'test'
...
# Subtest: row 323: real business validate accepts the Case 1 fixture without tracker credentials
ok 2 - row 323: real business validate accepts the Case 1 fixture without tracker credentials
---
duration_ms: 60.93225
type: 'test'
...
# Subtest: row 323: real factory install accepts the document's scratch input and writes it unchanged
ok 3 - row 323: real factory install accepts the document's scratch input and writes it unchanged
---
duration_ms: 166.799569
type: 'test'
...
# Subtest: row 323: the Case 1 business boots without a tracker or @sync credential
ok 4 - row 323: the Case 1 business boots without a tracker or @sync credential
---
duration_ms: 48.481068
type: 'test'
...
# Subtest: row 323: factory install refuses bus.tester false before writing
ok 5 - row 323: factory install refuses bus.tester false before writing
---
duration_ms: 33.722908
type: 'test'
...
# Subtest: row 323: literal library key refuses through final business validation
ok 6 - row 323: literal library key refuses through final business validation
---
duration_ms: 151.757296
type: 'test'
...
# Subtest: row 323: a library project refuses through the factory profile plan
ok 7 - row 323: a library project refuses through the factory profile plan
---
duration_ms: 33.339369
type: 'test'
...
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 613.804933
-90
View File
@@ -1,90 +0,0 @@
# Optional business tracker (Q247) implementation plan — row 324 / #1820
Goldie, 2026-10-23. Base 860afc2b66e7bbd045e9d5e25d227ef12f62c55b, approved row298 candidate. New worktree /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324, branch goldie/row324. Spec: git object f375f4e92:docs/plans/2026-10-23_optional-tracker-brief.md, section Brief: tracker becomes optional in the business file. Sage-3836 and mos-1555/Q247 authorize optional tracker; Duckworth reviews credential registration.
## Goal and design
A raw business document may omit tracker. Normalize omission to tracker:null, consistent with existing optional launch/role-tracker representations. A present tracker (including null, false, malformed objects) must continue through checkTracker and its existing refusals; distinguish absence with Object.hasOwn rather than a truthiness check. Keep checkTracker unchanged. Required top-level keys no longer include tracker. Only compare role bots with the sync bot when a checked business tracker exists. Role definitions needing vikunja still require their own tracker and credentials unchanged.
CLI validate collects sync credential refs only when tracker is present. bootConfig's trackerFor returns null before interpreting tracker variables when business tracker is absent, so an old tracker.baseUrl variable cannot accidentally create sync/task adapter configuration. Console businessView emits no sync credential rows for absent tracker. Runtime registration, fixed-configuration reload comparison, instance-settings bot collision checks and broker credential display already use optional chaining on the row298 base; preserve those checks. The tasks adapter remains fail-closed for an explicitly inconsistent raw adapter config, and no-tracker bootConfig never configures it for this business.
## Reader audit (before implementation)
Command: git grep -n -E 'tracker\.sync|\.tracker\b' -- 'packages/*/src/*'. Every result is grouped here, with base line numbers:
| Reader | Absent business tracker behavior / action |
| --- | --- |
| business/src/business.mjs:85-96,296-300 | checkTracker remains exact for present input; add omission normalization and guard own-sync-bot comparison. |
| business/src/business.mjs:143-150 | Per-role tracker, independent of business tracker. Keep requirement/collision checks unchanged. |
| business/src/cli.mjs:85 | Guard Object.values sync credential refs; absent contributes zero. |
| business/src/instance-settings.mjs:108,120,126-127,133,223 | Role add/change and bot collision/retirement: old.tracker?.sync already skips absent sync; role trackers unchanged. No edit needed. |
| business/src/launch-settings.mjs:161,235-236 | Per-role tracker assistant view/update, independent of business tracker; no edit. |
| business/src/resolve.mjs:93 | Per-role tracker in resolved output; no edit. |
| bus/src/runtime.mjs:131 | Existing b.tracker?.sync?.credentials check registers no @sync when absent; preserve and exercise with new boot test. |
| bus/src/runtime.mjs:243,247 | Reload compares per-role tracker/null and canonical business sync/null; safe already, no edit. |
| bus/src/broker.mjs:3969-3970 | Optional per-role tracker bot map; independent, no edit. |
| bus/src/broker.mjs:4497 | Optional per-role tracker returned from instance write; independent, no edit. |
| bus/src/broker.mjs:6242 | Credential display resolves optional sync bot; no @sync row exists on trackerless boot. Safe already. |
| cli/src/config.mjs:57-60,137-138 (trackerFor; not caught by literal grep) | Add business-tracker absence guard; do not configure tracker adapter even if tracker.baseUrl remains set. |
| cli/src/instances.mjs:118 | Builds per-role tracker update request only when user supplies bot; unrelated to business tracker. |
| tasks/src/adapter.mjs:25,28-30,35,45,81 | settings rejects missing sync with controlled tracker-config before dereferencing labels/botId; files uses optional chaining. Factory iterates only configured trackers; absent business never configured by bootConfig. Preserve explicit-inconsistent-config refusal. |
| webui/src/reads.mjs:164,166 | Role credentials independent; guard sync credential display at166 so trackerless Console does not throw. |
| webui/src/public/bus.js:785 | Per-role editor tracker patch; independent of business tracker. |
| conversation/src/controller.mjs:443,502,748,807,820,831,888,935,961,997,1008,1021,1048,1056,1268,1308,1330,1334,1413,1447 | Different execution-tracking object/class, unrelated to business/Vikunja config. No change. |
Positive control: grep finds business.mjs:296 and webui reads:166, the unsafe business-tracker readers. Review focus: present null/malformed tracker still refuses; per-role Vikunja trackers still required; stale tracker variables do not enable sync; Console credential view remains usable; no @sync registration during runtime boot/reload.
## Steps and evidence plan
1. Document design and full reader list (this file); claim row324 in-progress as Goldie.
2. Add new tests for absent tracker loader+CLI validate, malformed/present preservation, per-role tracker requirement, stale-variable bootConfig behavior, actual bus boot with empty credential list/no @sync, and Console business view. New test files only unless Mos expressly approves old-test change below.
3. Make the minimal four-source-file change (business loader, business CLI, cli config, webui reads). Existing runtime/instance-settings/broker optional checks require coverage, not gratuitous edits.
4. Static diff checks. Push untested candidate only as expressly ordered by sage-3836, then ask Sage for CLEAR. No tests before CLEAR.
5. On CLEAR, new test files whole, applicable existing whole files/suites, then a mutant removing loader presence check. Restore and check source bytes. Record outputs; full landing gate is Sage's. Duckworth review follows.
## Pre-existing test boundary
Inspection finds packages/business/tests/business.test.mjs:98 loops over [human,arbiters,projects,tracker,roles] and :99 asserts deletion of each refuses. Q247 directly invalidates the tracker iteration. Proposed sole old-test edit: remove just the tracker string from that required-key list, leaving all other iterations/assertions intact, and add explicit tracker-omission coverage in a new test file. This requires Mos OK under mos-1272; stop implementation and report before editing existing tests. No tests have run.
## Implementation draft and old-test ruling
Sage-3840 relays Mos mos-1556: mos-1272 OK to remove only tracker from business.test.mjs's required-key list, preserving the four other keys and the assertion. Done exactly, one addition/one deletion at original line98; no other old test edited. All positive/negative coverage is in two new files: business/tests/optional-tracker.test.mjs and cli/tests/optional-tracker.test.mjs. The latter covers Console businessView as required by sage-3840.
Design now implemented in the four source files identified above; README explains omission versus literal null. New tests cover loader/CLI absence, present value preservation, malformed/present undefined or null refusal, unchanged role-bot requirement, stale business/project tracker variables, actual runtime startup with no credential files or @sync reference, Console view, and continued missing-token refusal for a present tracker. Runtime test uses useFile on unknown @sync to distinguish an unregistered reference from a registered but unavailable credential; callback must never run. Fixture project tracker.project is set, so removing trackerFor's presence guard would actually configure sync instead of passing accidentally through the no-project guard.
No tests run. New node_modules symlink points to canonical checkout dependencies and is ignored; it will not be committed. Sage-3836 expressly requests push before any tests; that specific sequencing overrides the usual green-before-commit default only for this draft push. Status is untested until CLEAR. Claim wrote goldie-0324-start-1 revision3759, briefed→in-progress. Row323 remains paused at the product boundary until324 lands.
Static verification: node --check passed for all four edited source modules and both new test files; git diff --check passed. No node --test or shell suite was run. reader-audit.txt captures the requested grep on the draft. MANIFEST.sha256 pins README, four source files, the one existing test and both new tests. mutant.py prepares the required omission-presence mutant and restores/checks bytes in finally; it has not run.
Proposed CLEAR run order: new business optional-tracker.test.mjs whole (5 tests), new cli optional-tracker.test.mjs whole (4 tests), existing business.test.mjs whole, then the targeted presence mutant and restored new tests. Broaden to business/CLI/Console/bus package gates only when Sage assigns that scope/slot. All test runs use timeout -k 10 and an explicitly allocated TMPDIR under /mnt/storage/scratch/tmp.
## Rebased verification — sage-3850 CLEAR
Rebased c4b14952e onto pushed refactor `3569bf54f53870fcd4e92cdbbb95e3412f629f3a` as `7e59ba8b3d6f63c30a0fa308c0607dfc1e47dd12`. The landing range has no changes in this row's business/config/Console source paths. Only BUILD-LOG.md and docs/SESSIONS.md conflicted: retained the entire landed file then appended this row's original suffix. All eight manifest-pinned files remain byte-equal to the draft; range-diff differences are log context only. To obey no-force-push while preserving the published draft, the rebased candidate is published on `goldie/row324-r2`; original remote goldie/row324 remains at c4b14952e.
Scratch TMPDIR was created by `mktemp -d /mnt/storage/scratch/tmp/goldieXXXXXX`: `/mnt/storage/scratch/tmp/goldieOdgIjr`. All runs were serialized, with timeout -k 10 (120s new files/mutant driver, 180s existing whole file). Node v26.8.1. No test used /tmp or a real host configuration/credential.
| Order | Whole file / check | Result | Evidence |
| --- | --- | --- | --- |
| 1 | packages/business/tests/optional-tracker.test.mjs | 5/5 pass, exit0 | test-business-optional.log |
| 2 | packages/cli/tests/optional-tracker.test.mjs | 4/4 pass, exit0 | test-cli-optional.log |
| 3 | packages/business/tests/business.test.mjs | 21/21 pass, exit0 | test-business-existing.log |
| 4 | Presence-check mutant, targeted omission test | 0/1 pass, exit1, killed as expected | mutant.log, mutant-driver.log |
| 5 | Restored new business file whole | 5/5 pass, exit0 | test-business-restored.log |
| 6 | Restored new CLI file whole | 4/4 pass, exit0 | test-cli-restored.log |
No unexpected first-run red or old-test red. The Console absent-tracker read test is the third test in the new CLI file and passed on both whole-file runs. There is no separate edited webui test file; the only test files touched by this row are the three whole files above. Existing fixture preservation is covered by the whole business.test.mjs, including its untouched fixture, role bot, credential, sync bot collision and example checks. Broader package/script gates remain Sage's.
Required mutant diff (business/src/business.mjs:296):
```diff
- const tracker = Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null;
+ const tracker = checkTracker(document.tracker, file);
```
The new omission test fails with BusinessError `tracker must be a JSON object`. mutant.py restores in finally and checks exact bytes. Restored business.mjs SHA256: `e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a`. Full manifest check passes after restoration. git diff --check passes. The full reader list remains above; reader-audit.txt is its raw grep receipt.
The only pre-existing test delta still is exactly one list entry removed at business.test.mjs:98, under mos-1556: tracker is removed; human/arbiters/projects/roles and the assertion remain. No production/test source changed during verification. Only evidence and append-only log notes follow the tested rebased commit. Slot is returned in the completion report; Duckworth review and Sage's full landing gate remain pending.
Evidence log trailing whitespace is normalized for git diff --check; test results and output content are unchanged.
@@ -1,8 +0,0 @@
ecd63bcbeae04236c445e74493d425e4a5d1bfb723d72fb58446e1d020e4d429 packages/business/README.md
e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a packages/business/src/business.mjs
afef5c581beb26d0b0797faeb2acd3597597d48c0ce03cf31cce8d9e9b2690b9 packages/business/src/cli.mjs
2ec0c80361f12c0d90669a3c149dfc5c9607fc9dbad98ccd30d24cf14c0de9b6 packages/cli/src/config.mjs
46defeb3b4be0e1a22e4b89445872ad6b15cdee182076e9e6222219ae29997f4 packages/webui/src/reads.mjs
3f99c2d7e41b37f82c3f9edeac3bc795f982dac9a3728f7694cfd5668e944691 packages/business/tests/business.test.mjs
e92a976bcd6bf97c1cf7832d9e3fe4dec350d92809f26f7b43b938bad7e5dc49 packages/business/tests/optional-tracker.test.mjs
7723e299fb9a11cf313d3b26df8c0270b15982731dfc0af2463c96f23e1582a2 packages/cli/tests/optional-tracker.test.mjs
@@ -1,2 +0,0 @@
presence mutant: exit 1
restored sha256 e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a
-32
View File
@@ -1,32 +0,0 @@
TAP version 13
# Subtest: Q247: omitting tracker loads as null and preserves independent role bots
not ok 1 - Q247: omitting tracker loads as null and preserves independent role bots
---
duration_ms: 4.653674
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/tests/optional-tracker.test.mjs:21:1'
failureType: 'testCodeFailure'
error: '/mnt/storage/scratch/tmp/goldieOdgIjr/mosaic-business-IHRJco/config/businesses/acme.json tracker must be a JSON object'
code: 'ERR_TEST_FAILURE'
name: 'BusinessError'
stack: |-
refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/errors.mjs:12:9)
requireObject (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/util.mjs:11:30)
checkTracker (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/business.mjs:87:3)
validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/business.mjs:296:19)
loadBusiness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/src/business.mjs:325:10)
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324/packages/business/tests/optional-tracker.test.mjs:25:13)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 54.363835
-27
View File
@@ -1,27 +0,0 @@
"""Q247 targeted presence-check mutant; run only in an approved test slot."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
source = root / 'packages/business/src/business.mjs'
fixed = source.read_bytes()
needle = b'Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null'
assert fixed.count(needle) == 1
mutant = fixed.replace(needle, b'checkTracker(document.tracker, file)')
try:
source.write_bytes(mutant)
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
'--test-name-pattern=Q247: omitting tracker',
'packages/business/tests/optional-tracker.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Path(__file__).with_suffix('.log').write_text(result.stdout)
print(f'presence mutant: exit {result.returncode}', flush=True)
assert result.returncode == 1, result.stdout
assert '# fail 1\n' in result.stdout and 'tracker' in result.stdout, result.stdout
finally:
source.write_bytes(fixed)
assert source.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
@@ -1,62 +0,0 @@
packages/bus/src/broker.mjs:3969: .filter(([, r]) => r.tracker?.botId !== undefined)
packages/bus/src/broker.mjs:3970: .map(([role, r]) => [r.tracker.botId, role]),
packages/bus/src/broker.mjs:4497: return { transaction: out.transaction, retry: false, ...(out.tracker ? { bot: out.tracker.bot } : {}) };
packages/bus/src/broker.mjs:6242: bot = sync ? b.tracker?.sync?.bot : service === 'vikunja' ? b.roles?.[role]?.tracker?.bot : null;
packages/bus/src/runtime.mjs:131: if (b.tracker?.sync?.credentials) references[business + '/@sync'] = b.tracker.sync.credentials;
packages/bus/src/runtime.mjs:243: tracker: r?.tracker ?? null,
packages/bus/src/runtime.mjs:247: sync: canonicalJson(b.tracker?.sync ?? null),
packages/business/src/business.mjs:89: requireObject(tracker.sync, `${where}.sync`);
packages/business/src/business.mjs:90: rejectUnknownKeys(tracker.sync, ["bot", "botId", "credentials"], `${where}.sync`);
packages/business/src/business.mjs:92: ...checkBot({ bot: tracker.sync.bot, botId: tracker.sync.botId }, `${where}.sync`),
packages/business/src/business.mjs:93: credentials: checkCredentialMap(tracker.sync.credentials, ["vikunja"], `${where}.sync.credentials`),
packages/business/src/business.mjs:143: if (entry.tracker === undefined) refuse(`${at} needs "tracker" with its Vikunja bot, because ${entry.definition} uses vikunja`);
packages/business/src/business.mjs:144: tracker = checkBot(entry.tracker, `${at}.tracker`);
packages/business/src/business.mjs:145: if (bots.has(tracker.bot)) refuse(`${at}.tracker.bot ${tracker.bot} is used by another instance`);
packages/business/src/business.mjs:146: if (botIds.has(tracker.botId)) refuse(`${at}.tracker.botId ${tracker.botId} is used by another instance`);
packages/business/src/business.mjs:149: } else if (entry.tracker !== undefined) {
packages/business/src/business.mjs:296: const tracker = Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null;
packages/business/src/business.mjs:298: if (tracker && role.tracker && (role.tracker.bot === tracker.sync.bot || role.tracker.botId === tracker.sync.botId)) {
packages/business/src/business.mjs:299: refuse(`${file} tracker.sync must use its own bot, not the one roles.${instance} uses`);
packages/business/src/cli.mjs:85: ...Object.values(business.tracker?.sync?.credentials ?? {}),
packages/business/src/instance-settings.mjs:108:// needs a bus restart, then row 199's issue. A vikunja definition needs args.tracker, an existing
packages/business/src/instance-settings.mjs:120: tracker = checkBot(args.tracker, "tracker");
packages/business/src/instance-settings.mjs:126: const taken = Object.entries(old.roles).filter(([name, entry]) => name !== instance && entry.tracker).map(([name, entry]) => [name, entry.tracker]);
packages/business/src/instance-settings.mjs:127: if (old.tracker?.sync) taken.push(["tracker.sync", old.tracker.sync]);
packages/business/src/instance-settings.mjs:133: out.tracker = tracker;
packages/business/src/instance-settings.mjs:223: const bot = old.roles[instance]?.tracker?.bot;
packages/business/src/launch-settings.mjs:161: definition: entry.definition, vars: entry.vars ?? {}, tracker: entry.tracker ?? null,
packages/business/src/launch-settings.mjs:235: if (entry.tracker === null) delete next.roles[instance].tracker;
packages/business/src/launch-settings.mjs:236: else if (Object.hasOwn(entry, "tracker")) next.roles[instance].tracker = entry.tracker;
packages/business/src/resolve.mjs:93: tracker: entry.tracker,
packages/cli/src/config.mjs:58: if (!b.tracker) return null;
packages/cli/src/instances.mjs:118: if (bot) args.tracker = { bot: values["--bot"], botId: Number(values["--bot-id"]) };
packages/conversation/src/controller.mjs:443: exec.tracker = new Tracker({
packages/conversation/src/controller.mjs:502: if (exec.link.poisoned || exec.tracker.overlapped || this.closers.has("uncertain")) return;
packages/conversation/src/controller.mjs:748: return { fence: true, closer, fenceIdx: this.exec?.tracker.idx ?? 0 };
packages/conversation/src/controller.mjs:807: seam: { replay: "unavailable", streamEpoch: exec?.streamEpoch ?? null, fromSequence: (exec?.seq ?? 0) + 1, reconcile: true, quiet: !exec?.tracker.current && !exec?.tracker.slot },
packages/conversation/src/controller.mjs:820: if (exec?.tracker.slot) return refused(BUSY);
packages/conversation/src/controller.mjs:831: exec.tracker.attach(slot);
packages/conversation/src/controller.mjs:888: const tr = slot.exec.tracker;
packages/conversation/src/controller.mjs:935: const tr = exec.tracker;
packages/conversation/src/controller.mjs:961: const exec = slot.exec, tr = exec.tracker;
packages/conversation/src/controller.mjs:997: this.#settleFailure(exec.tracker.slot, RUN_OVERLAP);
packages/conversation/src/controller.mjs:1008: this.evidence.gaps.push({ kind, detail, idx: exec.tracker.idx });
packages/conversation/src/controller.mjs:1021: const tr = exec.tracker;
packages/conversation/src/controller.mjs:1048: const tr = exec.tracker;
packages/conversation/src/controller.mjs:1056: const tr = exec.tracker, s = tr.slot;
packages/conversation/src/controller.mjs:1268: const exec = this.exec, tr = exec?.tracker;
packages/conversation/src/controller.mjs:1308: const tr = exec.tracker;
packages/conversation/src/controller.mjs:1330: exec.tracker.clearWindow = Math.max(0, exec.tracker.clearWindow - 1);
packages/conversation/src/controller.mjs:1334: const { exec, stop } = ctx, tr = exec.tracker;
packages/conversation/src/controller.mjs:1413: const { exec } = ctx, tr = exec.tracker;
packages/conversation/src/controller.mjs:1447: const { exec, stop } = ctx, tr = exec.tracker;
packages/tasks/src/adapter.mjs:25: if (!positive(b.tracker?.sync?.botId)) throw new BusError('tracker-config');
packages/tasks/src/adapter.mjs:28: if (r.tracker) {
packages/tasks/src/adapter.mjs:29: if (!positive(r.tracker.botId) || typeof r.definition !== 'string') throw new BusError('tracker-config');
packages/tasks/src/adapter.mjs:30: roles[role] = { definition: r.definition, botId: r.tracker.botId };
packages/tasks/src/adapter.mjs:35: for (const [title, id] of Object.entries(b.tracker.labels ?? {})) {
packages/tasks/src/adapter.mjs:45: if (b.tracker?.sync?.credentials?.vikunja?.file) out['@sync'] = b.tracker.sync.credentials.vikunja.file;
packages/tasks/src/adapter.mjs:81: bots: new Set([businesses[name].tracker.sync.botId, ...Object.values(s.roles).map((r) => r.botId)]),
packages/webui/src/public/bus.js:785: if(f==='tracker'&&text.trim()===''){patch.tracker=null;continue;}
packages/webui/src/reads.mjs:164: for (const [service, ref] of Object.entries(r.credentials)) credentials.push(credential(service, ref, service === 'vikunja' ? r.tracker?.bot ?? null : null, instance, now));
packages/webui/src/reads.mjs:166: for (const [service, ref] of Object.entries(b.tracker?.sync?.credentials ?? {})) credentials.push(credential(service, ref, b.tracker.sync.bot, 'tracker sync', now));
@@ -1,136 +0,0 @@
TAP version 13
# Subtest: C2-04: library mappings load without roots, remain frozen, and retain mapping refusals
ok 1 - C2-04: library mappings load without roots, remain frozen, and retain mapping refusals
---
duration_ms: 10.358877
type: 'test'
...
# Subtest: C2-04: an otherwise valid publish grant cannot name a library repo
ok 2 - C2-04: an otherwise valid publish grant cannot name a library repo
---
duration_ms: 1.405968
type: 'test'
...
# Subtest: config directory and file path follow MOSAIC_CONFIG
ok 3 - config directory and file path follow MOSAIC_CONFIG
---
duration_ms: 0.194682
type: 'test'
...
# Subtest: the fixture business validates and comes back frozen
ok 4 - the fixture business validates and comes back frozen
---
duration_ms: 0.883527
type: 'test'
...
# Subtest: two instances may share a definition
ok 5 - two instances may share a definition
---
duration_ms: 0.869413
type: 'test'
...
# Subtest: top-level refusals
ok 6 - top-level refusals
---
duration_ms: 2.622608
type: 'test'
...
# Subtest: arbiters and projects
ok 7 - arbiters and projects
---
duration_ms: 3.523393
type: 'test'
...
# Subtest: role instances
ok 8 - role instances
---
duration_ms: 2.015683
type: 'test'
...
# Subtest: Vikunja bots
ok 9 - Vikunja bots
---
duration_ms: 4.64219
type: 'test'
...
# Subtest: a role without Vikunja takes no tracker block
ok 10 - a role without Vikunja takes no tracker block
---
duration_ms: 5.70969
type: 'test'
...
# Subtest: credential references match the definition's services
ok 11 - credential references match the definition's services
---
duration_ms: 1.94586
type: 'test'
...
# Subtest: launch
ok 12 - launch
---
duration_ms: 6.957271
type: 'test'
...
# Subtest: REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses
ok 13 - REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses
---
duration_ms: 3.58016
type: 'test'
...
# Subtest: loadBusiness: file checks
ok 14 - loadBusiness: file checks
---
duration_ms: 1.772126
type: 'test'
...
# Subtest: loadBusiness: not a regular file
ok 15 - loadBusiness: not a regular file
---
duration_ms: 40.121918
type: 'test'
...
# Subtest: loading writes nothing
ok 16 - loading writes nothing
---
duration_ms: 1.07771
type: 'test'
...
# Subtest: names that are Object.prototype properties don't count as declared
ok 17 - names that are Object.prototype properties don't count as declared
---
duration_ms: 3.194384
type: 'test'
...
# Subtest: the shipped example refuses as written and validates once filled in
ok 18 - the shipped example refuses as written and validates once filled in
---
duration_ms: 0.919964
type: 'test'
...
# Subtest: persona vars: agent layer, creative definition only, both or neither
ok 19 - persona vars: agent layer, creative definition only, both or neither
---
duration_ms: 7.866405
type: 'test'
...
# Subtest: row 194 keep, noHire and retired validate, and a file without them loads as before
ok 20 - row 194 keep, noHire and retired validate, and a file without them loads as before
---
duration_ms: 1.704708
type: 'test'
...
# Subtest: row 194 each bad keep, noHire or retired entry refuses
ok 21 - row 194 each bad keep, noHire or retired entry refuses
---
duration_ms: 5.095541
type: 'test'
...
1..21
# tests 21
# suites 0
# pass 21
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 159.214713
@@ -1,40 +0,0 @@
TAP version 13
# Subtest: Q247: omitting tracker loads as null and preserves independent role bots
ok 1 - Q247: omitting tracker loads as null and preserves independent role bots
---
duration_ms: 5.298841
type: 'test'
...
# Subtest: Q247: real business validate passes without a sync credential reference or file
ok 2 - Q247: real business validate passes without a sync credential reference or file
---
duration_ms: 71.475563
type: 'test'
...
# Subtest: Q247: present tracker retains its values and validation without mutating the input
ok 3 - Q247: present tracker retains its values and validation without mutating the input
---
duration_ms: 2.384907
type: 'test'
...
# Subtest: Q247: present null, undefined and malformed trackers still refuse
ok 4 - Q247: present null, undefined and malformed trackers still refuse
---
duration_ms: 3.746
type: 'test'
...
# Subtest: Q247: absent business tracker does not waive a role's Vikunja bot requirement
ok 5 - Q247: absent business tracker does not waive a role's Vikunja bot requirement
---
duration_ms: 0.804648
type: 'test'
...
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 135.467249
@@ -1,40 +0,0 @@
TAP version 13
# Subtest: Q247: omitting tracker loads as null and preserves independent role bots
ok 1 - Q247: omitting tracker loads as null and preserves independent role bots
---
duration_ms: 5.02203
type: 'test'
...
# Subtest: Q247: real business validate passes without a sync credential reference or file
ok 2 - Q247: real business validate passes without a sync credential reference or file
---
duration_ms: 68.916604
type: 'test'
...
# Subtest: Q247: present tracker retains its values and validation without mutating the input
ok 3 - Q247: present tracker retains its values and validation without mutating the input
---
duration_ms: 2.414133
type: 'test'
...
# Subtest: Q247: present null, undefined and malformed trackers still refuse
ok 4 - Q247: present null, undefined and malformed trackers still refuse
---
duration_ms: 3.820979
type: 'test'
...
# Subtest: Q247: absent business tracker does not waive a role's Vikunja bot requirement
ok 5 - Q247: absent business tracker does not waive a role's Vikunja bot requirement
---
duration_ms: 0.873886
type: 'test'
...
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 135.196828
@@ -1,34 +0,0 @@
TAP version 13
# Subtest: Q247: bootConfig omits tracker setup even with tracker variables left in the business
ok 1 - Q247: bootConfig omits tracker setup even with tracker variables left in the business
---
duration_ms: 5.854825
type: 'test'
...
# Subtest: Q247: trackerless business boots and registers no @sync credential
ok 2 - Q247: trackerless business boots and registers no @sync credential
---
duration_ms: 48.935736
type: 'test'
...
# Subtest: Q247: Console business read works with tracker absent and shows no sync credential
ok 3 - Q247: Console business read works with tracker absent and shows no sync credential
---
duration_ms: 1.663483
type: 'test'
...
# Subtest: Q247: a present tracker with a missing token still refuses bus boot
ok 4 - Q247: a present tracker with a missing token still refuses bus boot
---
duration_ms: 11.355417
type: 'test'
...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 226.91643
@@ -1,34 +0,0 @@
TAP version 13
# Subtest: Q247: bootConfig omits tracker setup even with tracker variables left in the business
ok 1 - Q247: bootConfig omits tracker setup even with tracker variables left in the business
---
duration_ms: 5.203349
type: 'test'
...
# Subtest: Q247: trackerless business boots and registers no @sync credential
ok 2 - Q247: trackerless business boots and registers no @sync credential
---
duration_ms: 46.033814
type: 'test'
...
# Subtest: Q247: Console business read works with tracker absent and shows no sync credential
ok 3 - Q247: Console business read works with tracker absent and shows no sync credential
---
duration_ms: 1.547096
type: 'test'
...
# Subtest: Q247: a present tracker with a missing token still refuses bus boot
ok 4 - Q247: a present tracker with a missing token still refuses bus boot
---
duration_ms: 11.051525
type: 'test'
...
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 195.359585
-48
View File
@@ -1,48 +0,0 @@
# Row 325 / #1821 — Case 1 project key
Goldie, 2026-10-24. Branch goldie/row325, base99930362e4b8a64d5425b10ca087b2d1f93ae538. Separate worktree /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325. Brief pinned at60d78e44e:docs/plans/2026-10-24_case1-project-key-brief.md, section Brief: re-key the Case1 project to stack. Mos-1563 fixes the business document and preserves project id. Reviewer Gladstone; Sage owns review posting/landing. No queue writes authorized or performed.
## Defect and change
The real .mosaic/project.json declares id stack; the Case1 business document incorrectly keyed its project mosaic-stack. Real business validation checks equality at packages/business/src/cli.mjs:80, so the install refused once the host held the project file. Row323's scratch execution root omitted that file and exercised only the warning/absent branch, missing the deployment mismatch.
Only the projects object's key changes to stack in docs/guides/case1-business.mosaic-stack.json. Business id stays mosaic-stack, root stays /srv/mosaic/src/mosaic-stack, and every other field is unchanged. No .mosaic/project.json edit or product/schema/role change.
The new packages/business/tests/case1-project-key.test.mjs reads the repo's real .mosaic/project.json bytes, asserts exactly one matching Case1 key, copies those bytes unchanged into a scratch project root, and calls the actual business CLI validate path with a temporary system/business config. Positive output must report the project valid, not absent. Negative coverage re-keys the same fixture to mosaic-stack and requires exit2 plus the real project-id mismatch message; missing-file warnings cannot stand in for validation.
Mos-1564 (mos-1272) authorizes exactly one pre-existing test line: case1-business-file.test.mjs:70's expected key changes to stack; all other bytes/assertions remain unchanged. No other old test is touched. This is explicitly the obsolete expectation caused by the product correction, not a relaxed assertion.
## Verification plan / state
No tests run before Sage CLEAR. Static node --check, JSON parse and diff checks only. On CLEAR, one run at a time: new test file whole, business suite whole (includes the one updated old test), one old-key mutant, restored whole new file to verify restoration. Every run gets fresh `export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)`, timeout-k10, and the established UTC/load/busy-helper log header. Stop/report on any other old-test red; no further old-test edits. First unexpected new-test red is recorded/reported under the continuing mos-1272 rule.
mutant.py changes only the projects key back to mosaic-stack, runs the new exact-key assertion, requires expected exit1/fail1, then restores/checks bytes in finally. Prepared, not run. Sage's landing gate supplies CLI suite counts.
The dependency setup is an ignored root node_modules symlink to /mnt/storage/src/mosaic-stack/node_modules; no dependency installation/pin change. MANIFEST.sha256 covers business JSON, the one old test, new test, and real project document as an unchanged reference. Final review report goes to both Sage and Gladstone with tip/base/manifest. No host contact, credential access, queue write or refactor push.
## Verification — sage-3882 CLEAR
All four runs were serial on the same final source bytes, using timeout-k10, fresh31-byte TMPDIRs from /mnt/storage/scratch/tmp/XXXXXX, and date-u/uptime/busy-helper headers. Node v26.8.1. No first-run new-test red and no old-test red. No CLI suite run; Sage's landing gate supplies that scope.
| Run / log | UTC start | Load averages | Busy | Result / TMPDIR |
| --- | --- | --- | --- | --- |
| test-new.log | 2026-10-24 01:13:40 | 1.34,1.60,2.50 | CLEAR | Whole new file3/3 pass, 172.161691ms; hchZu2 |
| test-business.log | 2026-10-24 01:13:50 | 1.36,1.59,2.49 | CLEAR | Whole business suite264/264 pass, 6478.924028ms; yWVbwY |
| mutant-driver.log + mutant.log | 2026-10-24 01:14:04 | 1.36,1.58,2.47 | CLEAR | Old-key mutant killed, targeted new test0/1, expected exit1; driver exit0; YsOE6N |
| test-restored.log | 2026-10-24 01:14:14 | 1.15,1.53,2.44 | CLEAR | Restored new file3/3 pass, 169.948442ms; Y43Jju |
All TMPDIRs are under /mnt/storage/scratch/tmp/. New/restored/mutant driver limits180s, business600s, each timeout-k10. Positive runs have0 failures/skips/cancellations. The business suite includes the entire old case1-business-file.test.mjs, with only the mos-1564-approved key assertion changed.
Mutant diff in business JSON:
```diff
"projects": {
- "stack": {
+ "mosaic-stack": {
```
The exact-key new test fails deepStrictEqual (actual mosaic-stack, expected stack); raw evidence is mutant.log. mutant.py restores in finally and checks exact bytes. Restored JSON SHA256 `9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f`; all4 MANIFEST.sha256 entries verify, including unchanged .mosaic/project.json. Whole new file passed again after restoration. git diff --check passed.
Final source accounting: business JSON1+/1-, only projects key; old test1+/1-, only expected key at70; new test file with3 cases; no other old-test line touched. Business id/root remain unchanged. Root-cause gap is now covered by byte-identical real project fixture plus required valid result and old-key refusal. No host action, credentials, queue writes, refactor push or unapproved scope change.
Logs have trailing whitespace normalized for git diff --check, with substantive output/results unchanged. The slot is returned in the report accompanying the pushed candidate; Gladstone first review and Sage landing gate remain pending.
@@ -1,4 +0,0 @@
9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f docs/guides/case1-business.mosaic-stack.json
9c3bf7f349d0ee8626743e1388b9a596c7f3c274beaedad5106db5f2ff8f65e7 packages/business/tests/case1-business-file.test.mjs
b543ac6afbfb336c5566f359ff357904b36f413d5f75fcd54a5a8b72aef1dbd6 packages/business/tests/case1-project-key.test.mjs
044714a37c267b70279977ea8ca429df7a28463271b884afd79a01bc27afa259 .mosaic/project.json
@@ -1,6 +0,0 @@
Sat Oct 24 01:14:04 UTC 2026
20:14:04 up 10 days, 11:33, 2 users, load average: 1.36, 1.58, 2.47
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/YsOE6N bytes=31
old-key mutant: exit 1
restored sha256 9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f
-40
View File
@@ -1,40 +0,0 @@
TAP version 13
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
not ok 1 - row 325: the Case 1 project's sole key matches the repository project id
---
duration_ms: 2.058961
type: 'test'
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325/packages/business/tests/case1-project-key.test.mjs:38:1'
failureType: 'testCodeFailure'
error: |-
Expected values to be strictly deep-equal:
+ actual - expected
[
+ 'mosaic-stack'
- 'stack'
]
code: 'ERR_ASSERTION'
name: 'AssertionError'
expected:
0: 'stack'
actual:
0: 'mosaic-stack'
operator: 'deepStrictEqual'
stack: |-
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325/packages/business/tests/case1-project-key.test.mjs:42:10)
Test.runInAsyncScope (node:async_hooks:226:14)
Test.run (node:internal/test_runner/test:1402:25)
Test.start (node:internal/test_runner/test:1262:17)
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
...
1..1
# tests 1
# suites 0
# pass 0
# fail 1
# cancelled 0
# skipped 0
# todo 0
# duration_ms 44.416722
-26
View File
@@ -1,26 +0,0 @@
"""Row 325 old-project-key mutant; run only in Sage's test slot."""
import hashlib
import os
from pathlib import Path
import subprocess
root = Path(__file__).resolve().parents[4]
source = root / 'docs/guides/case1-business.mosaic-stack.json'
fixed = source.read_bytes()
needle = b'"projects": {\n "stack": {'
assert fixed.count(needle) == 1
try:
source.write_bytes(fixed.replace(needle, b'"projects": {\n "mosaic-stack": {'))
result = subprocess.run([
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
'--test-name-pattern=row 325: the Case 1 project',
'packages/business/tests/case1-project-key.test.mjs',
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Path(__file__).with_suffix('.log').write_text(result.stdout)
print(f'old-key mutant: exit {result.returncode}', flush=True)
assert result.returncode == 1, result.stdout
assert '# fail 1\n' in result.stdout and 'mosaic-stack' in result.stdout, result.stdout
finally:
source.write_bytes(fixed)
assert source.read_bytes() == fixed
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
File diff suppressed because it is too large. Load diff
-32
View File
@@ -1,32 +0,0 @@
Sat Oct 24 01:13:40 UTC 2026
20:13:40 up 10 days, 11:33, 2 users, load average: 1.34, 1.60, 2.50
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/hchZu2 bytes=31
TAP version 13
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
ok 1 - row 325: the Case 1 project's sole key matches the repository project id
---
duration_ms: 4.262251
type: 'test'
...
# Subtest: row 325: real business validate reports the copied repository project valid
ok 2 - row 325: real business validate reports the copied repository project valid
---
duration_ms: 67.962164
type: 'test'
...
# Subtest: row 325: the old mosaic-stack project key refuses against those same project bytes
ok 3 - row 325: the old mosaic-stack project key refuses against those same project bytes
---
duration_ms: 57.926665
type: 'test'
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 172.161691
@@ -1,32 +0,0 @@
Sat Oct 24 01:14:14 UTC 2026
20:14:14 up 10 days, 11:34, 2 users, load average: 1.15, 1.53, 2.44
CLEAR
TMPDIR=/mnt/storage/scratch/tmp/Y43Jju bytes=31
TAP version 13
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
ok 1 - row 325: the Case 1 project's sole key matches the repository project id
---
duration_ms: 4.401838
type: 'test'
...
# Subtest: row 325: real business validate reports the copied repository project valid
ok 2 - row 325: real business validate reports the copied repository project valid
---
duration_ms: 60.555647
type: 'test'
...
# Subtest: row 325: the old mosaic-stack project key refuses against those same project bytes
ok 3 - row 325: the old mosaic-stack project key refuses against those same project bytes
---
duration_ms: 63.16989
type: 'test'
...
1..3
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 169.948442
-331
View File
@@ -1,331 +0,0 @@
# Row 226 build notes, C2-13b install script and library precheck (#1727)
Brief: docs/plans/2026-10-20_case2-rows.md "C2-13b install script and library precheck" (:1073-1134 at
d37210288). Design: agents/gosalyn/work/case2/DESIGN.md r11 sections 4 and 10, on gosalyn/case2-design
75fb3e10e. Row 195's needs: agents/darkwing/work/queue-195/DESIGN.md "What row 195 needs from row 226".
This is design round d2. Nothing is built yet. d1 was f46e66883, and Webby's review of it is webby-0085
(CHANGES, B1 and N1-N6) and webby-0086 (the ordering problem). Sage ruled on both in sage-2189. The
changes from d1 are listed at the end.
## Base
Branch gosalyn/c2-13b from refactor d37210288. Rows 215, 221, 222 and 230 are done.
## Facts the design rests on
- No CLI writes a business file. `mosaic business validate <id>` checks one and exits 0 ok, 2 invalid
(packages/business/src/cli.mjs). A business file needs `human`, `arbiters`, `projects`, `tracker`
with a sync bot and a Vikunja credential ref, and `roles`, each role with its tracker bot when its
definition uses Vikunja (business.mjs:23, :84-100, :131-149).
- The library is a project, `projects.<id>.kind === "library"`, checked by `checkMapping`
(packages/library/src/mapping.mjs). There is no top-level `library` key.
- `library.sync` and `library.status` are bus verbs, human only (broker.mjs:92, :2452-2476). The only
client is `packages/bus/src/human-cli.mjs` over the bus socket.
- The bus makes each library mirror at start, `ensureMirror({ business, project, dataRoot })`
(packages/bus/src/runtime.mjs:143-145). Before the bus first starts there is no mirror.
- `sync()` fetches outside `withLibraryLock`. Only a per-process `running` set keeps two syncs apart
(packages/library/src/sync.mjs:97-98), so a second process calling `sync()` would race the bus's
timer.
- `libraryStatus(context)` reads the recorded state under the flock and writes nothing
(mirror.mjs:232-239). A missing or unsafe mirror fails `library-git` (`ownedRoot`, mirror.mjs:43-49).
A mirror that has never synced returns the empty state, `lastFetch: null`, `lastError: null`,
`problems: []` (mirror.mjs:27, :205-210). `lastError` is `{ code, at }` with `code` matching
`^[a-z-]{1,64}$` (mirror.mjs:225).
- The bus builds the library context as `{ business, project, dataRoot }` and the slot as
`<slotDir>/secrets/<business.id>/library-gitea` (runtime.mjs:146-151, :162-164).
- `case2-persona-import.mjs install --staging DIR --config-dir DIR --business ID` writes
`<configDir>/personas/<business>/<instance>.md` at 0600 in 0700 dirs, never overwriting, and prints
`instance bean: persona.name Bean, persona.sha256 <hex>` per persona.
- A Discord binding is `<dataRoot>/discord/<name>.json` (packages/discord/src/binding.mjs:70-83).
- `case2-unit.sh --copy ID` needs `<home>/.local/share/mosaic-stack/copies/<ID>`, which
`stack-copy.sh copy COMMIT --copies DIR` makes. The copy runs `npm ci` from package-lock.json
(stack-copy.sh:70), which installs the pinned pi.
## Design
### D1. The business file is an input
Unchanged from d1. `case2-install.sh` takes `--business-file FILE`, a JSON business document written for
the host from Jason's go. The script doesn't build one from flags, because bots, botIds and credential
refs are host values and the brief says every host value is an input. It checks the input, adds what
only it knows, writes it, and then runs `mosaic business validate`, so business.mjs stays the only
schema.
What the script checks before it writes anything, beyond `validate`:
- `id` equals `--business`.
- `library`: at least one library project, `bus.tester` not true, roles `bean` and `chet` with
definition `companion`, and no `persona.*` var in the input, since D3 adds them.
- `factory`: no library project, and `bus.tester` true. These are the precheck's rules at
case2-precheck.sh:127-135, so the install refuses what the precheck would refuse.
### D2. Install steps, in order
1. Refuse to run as root.
2. `scripts/bootstrap.sh`. It's idempotent and never rewrites an existing config.
3. `scripts/stack-copy.sh copy <--commit> --copies <home>/.local/share/mosaic-stack/copies`. The copy
`case2-unit.sh --copy` names has to exist before root renders the units (row 195 need 3).
4. `library` only: the persona import's `install` step from `--personas DIR` (D3).
5. Write `<configDir>/businesses/<id>.json` at 0600 with O_EXCL (D4).
6. `library` only: make `<configDir>/secrets/<business>/` at 0700, empty. The token is Jason's, design 10
step 4.
7. `mosaic business validate <id>`. If it refuses and step 5 created the file in this run, remove that
file, then refuse. A file that already matched on a re-run (D4) is never removed (N4).
8. Print the copy id and the next steps: root's 2c, Jason's 2d, 3 and 4, then the `--library-status`
check (D6b).
d1's step 3, `release.sh package` and `activate`, is gone (Q4a, webby-0085 B1, sage-2189). Package is a
`docker compose build`, and activate runs a pi Compose health check that mounts `PI_AUTH_FILE`
(compose.yaml:43). Neither fits 2a, which has no Docker and no pi credential, and the hub doesn't run
containers. The pinned harness comes from the copy's `npm ci` (Q3). The `CASE2_RELEASE` seam goes with
it.
It starts no unit, writes no unit file, doesn't write `/etc/mosaic-stack/dev.args` and doesn't run
`adopt`. These are my answers to row 195's needs 4 and 5 (gosalyn-0087), unchanged. Row 195's W8 now
passes `--business-file` and has no release step.
### D3. Personas come from the staging dir
Unchanged from d1. `library` requires `--personas DIR`, the staging dir from `case2-persona-import.mjs
stage`. The install runs `case2-persona-import.mjs install --staging DIR --config-dir <configDir>
--business <id>` and reads its two `instance` lines into `roles.bean.vars` and `roles.chet.vars`. Only
those two lines go into the business file. They hold a name and a digest, never text.
This moves the persona import's stage part of design 10 step 6 ahead of 2b: the read of her Hermes
profiles and Jason's check of the printed pairing. Webby agreed in webby-0085 (Q1).
### D4. Re-runs
Unchanged from d1, plus N4. Each step is safe to run again. bootstrap and the copy are idempotent. An
existing business file that matches the one the script would write passes, and the script remembers it
didn't create it. One that differs refuses `business-file-differs`, and the script doesn't touch it.
The persona import already refuses to overwrite. On a re-run the install compares each existing
persona file's sha256 with the staging manifest, and refuses on a mismatch.
### D5. Inputs and seams
```
case2-install.sh --business ID --business-file FILE --commit SHA [--profile factory|library]
[--personas DIR] [--home DIR]
```
`--home` defaults to `$HOME`, which is `/srv/mosaic` under `sudo -u mosaic -H`. `MOSAIC_CONFIG` is
honoured as bootstrap.sh honours it. One test seam, in case2-unit.sh's `CASE2_*` pattern:
`CASE2_STACK_COPY` is the copy script, and the test uses a stub that makes the copy dir.
Exit codes: 0 ok, 1 refused, 4 usage, as case2-unit.sh uses.
### D6. Precheck library checks, split in two (Q4b, sage-2189)
d1 ran `sync()` from the precheck. Webby's N1 and webby-0086 showed two faults. The precheck runs before
the units start, so there is no mirror yet (runtime.mjs:143-145). And a precheck `sync()` after the bus
is up would race the bus's timer, since `sync()` fetches outside the lock (sync.mjs:97-98). Sage split
the check in sage-2189, and the brief's "runs library.sync" becomes "reads the bus's recorded sync
state".
#### D6a. Before the units start, `--profile library`
These run where the precheck runs today, after 2c's render and before the start. They sync nothing and
read no mirror. Each is a `refuse` line in the script's style.
- The business file is owned by the uid, with no group or other bit (`business-file-mode`).
`businesses/.history/<id>/` is 0700 and its `changes.jsonl` is 0600, when present (`history-mode`).
- The persona dirs are 0700. Each creative role's persona file is regular, owned and 0600
(`persona-mode`), and its sha256 equals the role's `persona.sha256` (`persona-mismatch`). The output
prints names and digests, never text.
- No `<dataRoot>/discord/*.json` exists (`discord-binding`).
#### D6b. `case2-precheck.sh --business ID --library-status`, after the bus is up and after step 4
A new mode, run as `<user>` once the bus is up and Jason has placed the token. It changes nothing. A
node block builds the context the bus builds, `{ business, project, dataRoot }` for each library
project, with `dataRoot` from the config (N3, runtime.mjs:146-151, :162-164). For each one it:
- refuses `library-mirror-missing` when the mirror path doesn't exist. It checks this itself, so the
message says the bus hasn't made it, not `library-git`;
- calls `libraryStatus(context)` only, and refuses with the thrown code when that fails, for example
`library-git` for an unsafe mirror or `library-sync-busy` when the flock wait runs out;
- refuses `library-never-synced` when `lastFetch` is null;
- refuses `library-last-error <code>` when `lastError` is not null. The code is the fixed
`^[a-z-]{1,64}$` string, so it's safe to print. This also covers Webby's N2;
- prints each recorded R-problem as `problem R<n> <path>`, with the path through `JSON.stringify`, then
one `problems <count>` line (N5). Her file names are the only part that prints, never text.
R-problems don't refuse (Q2, agreed by Webby).
The bus syncs each library with a `user` every 60 s, so the first run after step 4 may need a minute,
or Jason's own `library.sync`. The run sheet says so.
### D7. host-run-sheet.md fix
`docs/guides/host-run-sheet.md:240` says "I2's install writes it" about `dev.args`. It becomes: root
writes it at the unit render, from the values the go names, and the install never writes it, because
`<user>` can't write `/etc` and root reads nothing `<user>` wrote. Sage asked in sage-1832 for this fix
to go in this build commit, with its path in the manifest. Section 9's `library-gitea` line (:275) says
"C2-13b's `library.sync` (not landed)". It becomes the `--library-status` check (D6b). The install
section also gets a line that the copy's `npm ci` needs the npm registry from the host (Q3, webby-0085).
### D8. Design 10 text
Sage ruled that design 10 gets the same change when 226 lands, with the line in this row's manifest.
In agents/gosalyn/work/case2/DESIGN.md:
- step 2b drops `release.sh package` and `activate`;
- a new step after step 4 runs `case2-precheck.sh --business <business> --library-status`;
- the precheck's library text says it reads the bus's recorded sync state, not that it runs
`library.sync`.
### D9. Tests, `packages/cli/tests/case2-install.test.mjs`
These follow case2-unit.test.mjs: scratch HOME, MOSAIC_CONFIG, stub bin dir and seams, a local bare repo
cloned `--bare --no-local` as sync.test.mjs does, and the fixture's key never in output.
- Install `library`:
- writes the business file at 0600 with bean's and chet's persona vars;
- writes the persona files at 0600 in 0700 dirs and the empty slot dir at 0700;
- logs the copy call and no release call;
- starts nothing;
- passes on a re-run;
- refuses `business-file-differs` and leaves the file unchanged.
- Install `factory`:
- writes no library mapping, no persona file and no slot dir, which is the brief's case;
- refuses a library project and `bus.tester` off.
- Install refuses: run as root, a business id mismatch, missing `--personas` under library, and a
`validate` refusal, after which no business file is left.
- N4: a re-run where the matching business file is already there and `validate` refuses leaves that
file in place, byte for byte.
- D6a refusals, one case each: a persona file at 0644, a digest mismatch, a business file at 0644, a
`changes.jsonl` at 0644, and a Discord binding. A clean library host passes D6a with no mirror.
- D6b, with the mirror made by `ensureMirror` and the state written by a real `sync()` against the local
bare repo, as the bus would:
- no mirror refuses `library-mirror-missing`;
- a fresh mirror refuses `library-never-synced`;
- a sync against a remote that doesn't resolve refuses `library-last-error` with its code;
- a synced bare repo with one R2 path prints its `problem` line and `problems 1`, and passes.
Every new refusal gets a mutant that removes it. The count goes in the slot request.
### D10. Files
Owned by the brief: `scripts/case2-install.sh` (new), `scripts/case2-precheck.sh` (library checks and
`--library-status` only) and `packages/cli/tests/case2-install.test.mjs` (new). Also
`docs/guides/host-run-sheet.md` (D7, sage-1832), agents/gosalyn/work/case2/DESIGN.md (D8, sage-2189)
and this BUILD.md. Before the slot request I'll grep for outside readers of case2-precheck.sh, since
case2-unit.test.mjs runs it.
## Questions
All three d1 questions are settled:
- Q1, persona stage before 2b: Webby agreed (webby-0085).
- Q2, R-problems don't refuse: Webby agreed.
- Q3, the pinned harness is the copy's `npm ci`: Webby agreed, and asked for the registry note in D7.
- Q4a and Q4b: Sage, sage-2189.
One choice for Webby to check: D6b's `problem` line carries the path through `JSON.stringify`. N5 asked
for a prefix and a count. I kept the path because Jason needs it to tell her which file to fix, and the
names are hers, not text.
## Changes from d1
- B1: release package and activate dropped, with the `CASE2_RELEASE` seam and its test (D2, D5).
- N1 and webby-0086: the sync is gone from the precheck. D6 splits into D6a before the start and D6b,
`--library-status`, after the bus is up and step 4 (sage-2189).
- N2: covered by D6b's `lastError` refusal.
- N3: D6b builds the context and slot as the bus does.
- N4: step 7 removes only a file this run created, with a test.
- N5: `problem` prefix and a `problems` count.
- N6: the run sheet lines are :240 and :275.
- New D8, the design 10 text change, and its path in D10.
## Build
Webby approved d2 in webby-0087 (REVIEW-design-d2.md, webby/reviews 2f488b4ea). Nothing above this
section changed after approval. This section records the build.
### D8 lands on case2-design
Sage ruled in sage-2195, after Webby's N1, that D8 goes on gosalyn/case2-design, because
agents/gosalyn/work/case2/DESIGN.md isn't on refactor. The change is commit 624212842 there, pushed:
step 2b drops `release.sh package` and `activate`, new step 4a runs `--library-status`, and the C2-13b
row text says the precheck reads the bus's recorded sync state. DESIGN.md is not in this row's manifest.
The brief's lines docs/plans/2026-10-20_case2-rows.md:1093-1096 still name release package, activate and
a library.sync fetch. Sage owns the brief and will amend those lines to match D8 in a doc-only commit
after the re-pin window (sage-2205). They are not in this row's manifest.
### Webby's d2 notes
- N2: the `problem` line keeps the path. After `JSON.stringify` the precheck also escapes DEL, the C1
controls U+0080-009F, U+200E and U+200F, U+2028 and U+2029, and the bidi controls U+202A-202E and
U+2066-2069 as `\uXXXX`. Webby named C1 and bidi. I added the rest because `JSON.stringify` leaves
them raw too, and each one can change how a terminal shows the line. The test path holds U+202E and
U+0085, and the test checks that neither prints raw.
- N3: `lastError` is checked before `lastFetch`. A first sync with no token records
`library-credential-missing` and leaves `lastFetch` null, so it refuses `library-last-error`, not
`library-never-synced`. The fresh-mirror test has `lastError` null and refuses `library-never-synced`.
### Choices made in the build
- The config dir. The persona import refuses a config dir with group or other bits, and bootstrap makes
one with the default mode. So the install makes the config dir at 0700 when it's absent, before
bootstrap, and refuses `config-dir-mode` when it exists with a group or other bit. It checks
`secrets/` and `secrets/<id>/` the same way under library (`secrets-mode`, `slot-mode`).
- `--home` must equal `$HOME` (`home-mismatch`). Every path comes from `$HOME` and `MOSAIC_CONFIG`, as
bootstrap's does, so a different `--home` would only name a second home the script doesn't use.
- A plan step runs before anything is written. It reads the input and the staging manifest, computes
the persona vars from the manifest, and compares the result with what's on disk. It refuses every
input rule, `business-file-differs` and `persona-differs` there, so a refused run writes nothing.
After the import the script checks that the import's two `instance` lines carry the manifest's
digests.
- A re-run with both persona files present and matching skips the import, which would refuse to
overwrite them. One matching file and one missing refuses `persona-differs`.
- A diverged library prints one line under `--library-status`, naming the tip `library.sync --accept`
would take. It doesn't refuse. The design doesn't list it as a refusal, and the bus still serves the
old tip.
- The precheck's library checks print `ok` lines for each check they pass, as the rest of the script
does. A business file that's missing prints no extra line, since the existing business-file check
already refuses it.
- The business id must match the persona import's rule, `^[a-z0-9][a-z0-9-]{0,63}$`. That's narrower
than the precheck's, which also takes `.` and `_`, because the persona dir uses the id.
### Outside readers
`git grep` over packages/*/tests/*, packages/*/src/* and scripts/test-*.sh finds case2-precheck.sh read
by case2-unit.test.mjs, which runs it, and case1-accept.test.mjs, which names a copy's path but runs a
fake. No test reads host-run-sheet.md. No test pins a hash of either script. case2-unit.test.mjs's
precheck host has a 0600 business file and no persona vars, so the new D6a checks pass there.
### Mutants
24, in /mnt/storage/scratch/gosalyn-r226/mutate.py. Each replaces one exact string once. Line numbers
are the candidate's. run.sh stops with BASE RED before any mutant unless the base run is rc=0 and
fail 0.
| Mutant | Line | Check removed |
|---|---|---|
| I1 | install :73 | run-as-root |
| I2 | install :74 | home-mismatch |
| I3 | install :81 | config-dir-mode |
| I4 | install :101 | business-id-mismatch |
| I5 | install :107 | profile-library-business |
| I6 | install :108 | bus-tester-off |
| I7 | install :111 | bus-tester-on |
| I8 | install :115 | persona-var-input |
| I9 | install :158 | business-file-differs |
| I10 | install :152 | persona-differs |
| I11 | install :218 | N4, removes the file even when this run didn't write it |
| I12 | install :218 | validate's removal of the file this run wrote |
| P1 | precheck :229 | business-file-mode |
| P2 | precheck :235 | history-mode for changes.jsonl |
| P3 | precheck :246 | persona-mode for a persona file |
| P4 | precheck :251 | persona-mismatch |
| P5 | precheck :241 | persona-mode for a persona dir |
| P6 | precheck :258 | discord-binding |
| P7 | precheck :104 | library-mirror-missing |
| P8 | precheck :107 | library-last-error |
| P9 | precheck :107 | N3, the order swapped |
| P10 | precheck :108 | library-never-synced |
| P11 | precheck :92 | N2, the escape after stringify |
| P12 | precheck :65 | --library-status refuses --profile |
`history-mode` for the history dir itself has no mutant and no test. The brief names only the
`changes.jsonl` case.
### Tests
Not run yet. They wait for a slot.
-177
View File
@@ -1,177 +0,0 @@
# Row 239 (#1745) build notes (append only)
## 2026-10-22 b1 (Honker)
Design d1 5da5f28f6 was approved by Duckworth with C1 and C2 and no blockers (duckworth-0165, note
agents/duckworth/work/row239/REVIEW-design-d1.md). The code is on honker/row239 from aca16404c. No test
has run yet: every run waits for Sage's CLEAR.
### Duckworth's conditions and answers
- C1 (option a): `outcome-unknown-revoked` is in the Console's `OPEN` list (webui bus.mjs), so
`sendOutcome` answers it as `outcome-unknown` with `sent: 'unknown'` and never "Nothing changed". T6
covers it and M3f is its mutant.
- C2: `write()` sets `revoked = true` after `beforeWrite()` returns, and a catch around the whole
`withLock(...)` call marks the error. So a throw from the lock's release in its finally is marked as
well. This is structural and has no test, as Duckworth said.
- Q1: `beforeWrite` runs after both validations. Q2: two codes. Q3: M2 removes the `:188` check (now
organization.mjs:195).
### Notes N1-N5
- N1: `beforeWrite` must not write the same business, because it would find its own lock and refuse
`organization-busy`. This is said in the comments on `write()` and `commit()`. Today's revokes (the
bus's credential revoke) write no business file.
- N2: the lock is held across the revoke. A fire or a delete now keeps other writers of that business
out for as long as the revoke takes. They refuse `organization-busy`, as they do for any held lock.
- N3: a revoke that fails partway (some tokens revoked, then `credential-revoke-failed`) behaves as
today. The error carries no `revoked` mark, because `beforeWrite` didn't return, and nothing is
written. This row doesn't change that.
- N4: a delete closed with `outcome-unknown-revoked` exits 3 in the CLI, as every closed refusal does.
Exit 1 (transport EXIT) applies only to a direct verb answer.
- N5: I grepped for "before the write" and similar comments. The ones left (lifecycle-revoke.test:2,
instance-settings:56, cli instances.mjs:53, serve.mjs:365) are still true and are unchanged. T21 in
bus lifecycle-revoke.test:173 is unchanged.
### Differences from the design
- The business tests are in lifecycle-settings.test.mjs, not instance-settings.test.mjs. The fire tests
and their fixture already live there. The fixture now returns `dir` and `rolesDir` too.
- T3 covers T3b (the delete) in the same test, looping over fire and delete. It also checks that a
revoke throwing `credential-revoke-failed` carries no mark and writes nothing.
- T4b is the plain-code check. The design's "T4b, outcome-unknown-revoked for an fs-style error" is in
T4, which checks EIO with and without the mark for both a fire and a delete.
- The bus test's fake org gets `deleteError`, thrown from its `instances.set` branch, so T4 can drive a
delete.
- transport.test gets one assertion, `busExit("outcome-unknown-revoked") === 1`, for M3g.
- A latent Console text: a direct (not closed-request) `revision-conflict-revoked` answer in the
Console would still get " Nothing changed." appended, because only `OPEN` codes skip it. That sentence
is true there: the file is unchanged. No direct human path gives this code today, since a delete
reaches the Console as a closed request's refusal.
- The Console's STATUS entry for `outcome-unknown-revoked` (503) can't be reached while the code is in
`OPEN`. It stays for symmetry with the CLI and is harmless.
### What changed
- packages/business/src/organization.mjs: `write()` takes `beforeWrite`, runs it under the lock after
the revision check and both validations, and marks any later error `revoked`. The body is reindented
inside the try.
- packages/business/src/instance-settings.mjs: `instanceSettings` takes an optional `writer`. `commit()`
passes `beforeWrite` to `writer.write` instead of calling it. The early check stays, and its comment
is rewritten.
- packages/bus/src/broker.mjs: `#organizationFail` maps a marked error to `revision-conflict-revoked` or
`outcome-unknown-revoked` before the existing mappings. `#fireJob`'s notice adds "Its credential
tokens may already be revoked." for a `-revoked` code.
- packages/bus/src/runtime.mjs: comment only.
- packages/cli/src/instances.mjs: two REASONS entries. packages/cli/src/transport.mjs: EXIT
`outcome-unknown-revoked` 1.
- packages/webui/src/serve.mjs: two AGENT_REASONS entries. packages/webui/src/bus.mjs: `OPEN` entry and
two STATUS entries.
- Tests:
- business lifecycle-settings.test: "row 239 T1", "row 239 T2", "row 239 T3".
- bus fire-revokes-grants.test: "row 239 T4", "row 239 T4b".
- cli instances.test: "row 239 T5 CLI". cli transport.test: one assertion in "busExit and
refuseInsideAgent".
- webui agents.test: "row 239 T6".
- bus channel-proof.test re-pins packages/cli/src/transport.mjs (662642a8...) and
packages/webui/src/bus.mjs (1eba9774...), with a comment on each. Neither changes a proof or the
transport. The design's file list missed this. The pin grep found it before the first run.
### Mutants for the gate (exact-once replacements)
- M1 (instance-settings.mjs, beforeWrite before the lock): replace
` const receipt = writer.write(business, next, { revision, actor, context, beforeWrite });` with
` if (beforeWrite) beforeWrite();\n const receipt = writer.write(business, next, { revision, actor, context });`.
The kill is lifecycle-settings "row 239 T1" (the revoke spy has a call).
- M2 (organization.mjs, the fire refusal dropped): delete the line
` if (revision !== before) fail("revision-conflict", "organization revision changed; read again");`.
The kill is "row 239 T1" (the write lands over the hand edit).
- M3a (organization.mjs, the mark dropped): ` if (revoked && e instanceof Error) e.revoked = true;`
becomes ` void revoked;`. The kills are "row 239 T2" and "row 239 T3" (business). T4 drives the mark
through a fake org, so it doesn't see M3a.
- M3b (broker.mjs): delete the line
` if (e.revoked) fail(e.code === "revision-conflict" ? "revision-conflict-revoked" : "outcome-unknown-revoked");`.
The kill is fire-revokes-grants "row 239 T4".
- M3c (cli instances.mjs): delete the five lines from ` "revision-conflict-revoked": "the business file`
through ` "business file again with \`mosaic business instances\` before trying again",`. The kill is
instances.test "row 239 T5 CLI".
- M3d (webui serve.mjs): delete the two lines starting ` 'revision-conflict-revoked': 'the business file`
and ` 'outcome-unknown-revoked': 'the agent\'s tokens`. The kill is agents.test "row 239 T6".
- M3e (broker.mjs): ` const tokens = code.endsWith('-revoked') ? ' Its credential tokens may already be revoked.' : '';`
becomes ` const tokens = '';`. The kill is "row 239 T4".
- M3f (webui bus.mjs, C1): the `OPEN` line loses `, 'outcome-unknown-revoked'`. The kill is "row 239 T6"
(the launch POST answers 503 outcome-unknown, sent unknown).
- M3g (cli transport.mjs): delete the line ` "outcome-unknown-revoked": 1,`. The kill is transport.test
"busExit and refuseInsideAgent".
A kill counts only as a failed assertion in the named test, not a load error or a different test.
### Gate plan (mos-1275 timeouts)
- My row 256 gate (2026-10-22 10:34Z, same machine) took 590s for the cli package, 57s for webui and
38s for test-foundation, the longest shell script.
- Packages: `timeout -k 10 1800` per package (3x the cli run).
- Each scripts/test-*.sh: `timeout -k 10 300` (more than 3x the 38s longest).
- Single files and mutants: `timeout -k 10 600` per file run (cli instances.test whole is the longest
single file).
- Order on Sage's CLEAR: the touched test files whole (business lifecycle-settings, bus
fire-revokes-grants and channel-proof, cli instances and transport, webui agents), then M1-M3g, then
the full gate.
- Before the in-review move: grep packages/*/tests for the changed comment and error text and for
64-hex pins on the changed files, and drift-check BASE..refactor against them.
## 2026-10-22 r2 rebase (Honker)
- Duckworth approved b1 b0c28366f statically, with no blockers (duckworth-0168). On N1 (`endsWith('-revoked')`
would also match `access-revoked`, which can't reach a fire job today), the check stays as it is, so the
approved candidate doesn't change before the gate (honker-0163).
- Rows 253 (48e450ce8) and 275 landed. b1 is rebased onto refactor 94c33a43c as honker/row239-r2. The
push branch honker/row239 isn't rewritten.
- Drift between aca16404c and 94c33a43c on the 15 paths: only packages/bus/src/broker.mjs, where row 275
changes the viewer verbs. Neither of row 239's two hunks (`#fireJob` and `#organizationFail`) moves in
meaning. The rebase applied with no conflict. The two pinned files (cli transport.mjs, webui bus.mjs)
and channel-proof.test have no upstream change, so the b1 pins stand.
- Each M1-M3g replacement string still matches exactly once.
- The touched test files run whole again on the new base, in the slot (the rebase rule).
## 2026-10-23 r3 rebase (Honker, sage-3730)
- r2 (70c6e3772) is rebased onto refactor 93694c518 as honker/row239-r3. honker/row239-r2 isn't rewritten.
- Between 94c33a43c and 93694c518, five of the 16 paths changed upstream: bus broker.mjs and runtime.mjs,
channel-proof.test, and webui bus.mjs and serve.mjs. Every file merged on its own except channel-proof.test,
where row 301 (59f0e44f4, mos-1449) re-pinned webui bus.mjs for the user transport fold. The merged
pin keeps row 301's comment and row 239's, and takes sha256 e3f52172…1c2a89 of the merged bus.mjs.
The transport.mjs pin (662642a8…6e55b) still matches the file.
- Row 239's hunks in broker.mjs (`#fireJob`, `#organizationFail`), runtime.mjs (the revoke comment),
bus.mjs (OPEN, STATUS) and serve.mjs are unchanged apart from their position. The range-diff shows
only the channel-proof pin and the hunk context moved.
- Each M1-M3g replacement string still matches exactly once. M3f's target is the whole `OPEN` line,
since `, 'outcome-unknown-revoked'` also appears in the STATUS line.
- No 64-hex pin in packages/*/tests or scripts names a changed file except channel-proof.test.
- On the CLEAR (sage-3730): the touched files whole, then M1-M3g, then the counts here. Sage runs the
full gate on the replay at landing.
## 2026-10-23 r4 replay and the slot run (Honker, sage-3773 CLEAR)
- On the CLEAR, refactor was 546366f4a (row 320 landed at 094124b4e). None of the 16 manifest paths changed
between 93694c518 and 546366f4a. Upstream did change library src/index.mjs, which bus runtime.mjs imports,
and business src/index.mjs, which the cli imports, so the run was on a replay onto 546366f4a rather than
on r3's base. That replay is honker/row239-r4 0cae2dad5. The range-diff of r3 against r4 is four `=`
lines with equal patch-ids, so duckworth-0292's read of r3 applies to r4. r3 isn't rewritten.
- The run was on 0cae2dad5 with a clean tree, TMPDIR under /mnt/storage/scratch/tmp, 22:01:27Z-22:01:52Z.
- The touched files, run whole: business lifecycle-settings 12/12, bus fire-revokes-grants 24/24, bus
channel-proof 2/2, cli instances 15/15, cli transport 3/3, webui agents 9/9. That's 65/65 with 0 skipped,
and no first-run red.
- Mutants: 9 of 9 killed. Each was applied exactly once, and each made its named test fail as an assertion:
- M1 lifecycle: 3 fail (T1, T2, T3).
- M2: T1.
- M3a: T2 and T3.
- M3b fire-revokes-grants: T4.
- M3c instances: T5 CLI.
- M3d agents: T6.
- M3e: T4.
- M3f agents: T6.
- M3g transport: "busExit and refuseInsideAgent".
T4b passed under M3b and M3e. Every restore matched the HEAD blob by sha256, and the tree was clean
after the run.
- No full gate (sage-3730): Sage runs it on the replay at landing.
-159
View File
@@ -1,159 +0,0 @@
# Row 239 design: the revoke under the writer's lock, and a fire test
Brief: docs/plans/2026-10-20_agent-lifecycle.md, "Brief: row 194 follow-up, the revoke under the writer's
lock and a fire test" (blob 3b3b3b2b9), from Duckworth's row 194 b3 N1 and mos-1158. Base aca16404c (row
194 landed as fd6dd327c, row 281 as e479521d3). Builder Honker, reviewer Duckworth, gate owner Sage.
Draft 1, 2026-10-22.
## Today
- `instance-settings.mjs` `commit()` runs the checks, then compares the caller's revision with a fresh
`writer.read` (row 194 b3, sage-1634), then calls `beforeWrite` (the fire's or the delete's credential
revoke), then `writer.write`.
- `writer.write` (`organization.mjs`) takes the lock, runs `recover`, checks the revision again (`:188`),
validates, prepares, checks the bytes once more before publication (`:221`), renames and commits.
- So two windows are left.
- A write that lands between `commit()`'s read and the writer's lock still gets a revoke and then a
`revision-conflict`.
- Any refusal after the revoke ran (the `:221` check, or an I/O failure the bus reports as
`outcome-unknown`) uses the same text as one before it, so nothing says the tokens may be gone.
- Row 281 left this path alone. Its fire job (`broker.mjs` `#fireJob`) runs step G (grants), then step B,
which is this same `write('instance.fire')`, and reports a step B failure as `fire.failed` along with a
human notice that names the code. Its delete job only does step G. The delete itself still commits
through `lifecycleDelete` → `instances.set` op delete → `commit()`.
## What changes
### 1. `beforeWrite` runs inside `writer.write`, under the lock (organization.mjs)
`write(id, document, { revision, actor, detail, context, beforeWrite = null })`. The order under the lock:
1. `recover`, read, then the `:188` revision check. A refusal here revokes nothing.
2. Projects-protected and both validations. A refusal here revokes nothing either. I put `beforeWrite`
after these rather than straight after `:188`, so a document the writer would refuse anyway never
costs a revoke. Q1 asks Duckworth to confirm this.
3. `beforeWrite()`. If it throws (today `credential-revoke-failed`), the error propagates as it is and
nothing has been written, as now.
4. Prepare, the `:221` check, rename and commit, wrapped so that any error thrown after step 3 gets
`revoked = true` set on it before it is rethrown. That covers `revision-conflict` from `:221`, an
`organization-refused` from `log()`, a barrier throw, and an fs error.
`record()` and the other writers (`launch-settings`, `plugin-settings`, `credentialVerbs`) pass no
`beforeWrite` and behave as before.
### 2. commit() passes beforeWrite through (instance-settings.mjs)
`commit()` passes `beforeWrite` to `writer.write` and no longer calls it itself. The row 194 b3 early check
stays. It costs one read, keeps T21's refusal before any validation work, and is no longer what protects
the revoke. Its comment is rewritten to say so, and the "(BUILD.md follow-up)" sentence goes.
`instanceSettings({ ..., writer = organizationWriter({ dir, rolesDir }) })` takes an optional writer, so a
test can inject one (the brief's "injected writer"). `runtime.mjs` passes none.
### 3. The bus names a refusal after a revoke (broker.mjs `#organizationFail`)
The bus answers with a code only, and the CLI and Console look the text up by code. So the "may already be
revoked" signal has to be a code. Two new codes:
- `revision-conflict-revoked`: `e.revoked && e.code === 'revision-conflict'`.
- `outcome-unknown-revoked`: `e.revoked` with any other code. The write may or may not have landed, since a
failure after the rename is recovered as committed.
Both are checked before the existing mappings. An error without `revoked` maps exactly as today, so a
refusal before any revoke keeps today's code and text. Q2 asks about the names.
### 4. The texts
- CLI `instances.mjs` REASONS:
- `revision-conflict-revoked`: "the business file changed before the write, after the agent's tokens
were revoked; the tokens may already be revoked and the instance can't use them; read the instances
again and run the same command again, which is safe".
- `outcome-unknown-revoked`: "the write's outcome is unknown and the agent's tokens may already be
revoked; read the instances before trying again".
- Console `serve.mjs` AGENT_REASONS: the same two, in the Console's wording ("delete again").
- `#fireJob`'s `fire.failed` notice: when the code ends in `-revoked`, it adds "Its credential tokens may
already be revoked." The chief's synchronous fire (no grants) answers with the code, as every chief verb
does.
- `transport.mjs` EXIT: `outcome-unknown-revoked` exits 1, as `outcome-unknown` does. Console
`bus.mjs` STATUS: `revision-conflict-revoked` 409, `outcome-unknown-revoked` 503. A delete's code
reaches both as a closed request's `refusal`, so these two only matter for a direct verb answer.
## Tests
- **T1, the brief's fire test** (business `instance-settings.test.mjs`). The fire uses an injected writer
that wraps the real one. Its `write` makes one hand edit of the business file (a vars change on another
instance) and then calls the real `write`, so the edit lands after `commit()`'s early check and before
the lock's `:188` check. Expected:
- the fire refuses `revision-conflict` without `revoked`;
- the revoke spy has no calls;
- the business file's bytes are the hand edit's;
- changes.jsonl has no new prepared entry.
- **T2** (same file). A fire on a real writer built with `barrier: stage => stage === 'prepared' && edit()`.
The revoke runs, then `:221` refuses. Expected: `revision-conflict` with `revoked === true`, one revoke
call, the file's bytes are the edit's, and the last receipt is `aborted`.
- **T3** (same file). The same, with a `prepared` barrier that throws. Expected: the error has
`revoked === true`, one revoke call, and the file unchanged. A delete through the same writer gives the
same result (T3b).
- **T4** (bus `fire-revokes-grants.test.mjs`, its injected `f.org`). `f.org.fireError` is a
`revision-conflict` with `revoked: true`. Expected:
- the job's `fire.failed` carries `revision-conflict-revoked`, and the inbox message says "may already
be revoked";
- the same with no `grants` (the synchronous fire) refuses `revision-conflict-revoked`;
- a plain `revision-conflict` still maps to `revision-conflict` and the message lacks the sentence.
- T4b: `outcome-unknown-revoked` for an fs-style error with `revoked`.
- **T5** (cli `lifecycle.test.mjs` or `instances.test.mjs`, whichever already reads a closed delete's
refusal). A delete closed with `revision-conflict-revoked` prints the new text, and one closed with
`revision-conflict` prints today's.
- **T6** (webui `agents.test.mjs`). The same pair for the Console's reason.
- T21 (bus `lifecycle-revoke.test.mjs`) stays as it is, for delete.
## Mutants
| Mutant | Change | Killed by |
| --- | --- | --- |
| M1 | `beforeWrite` before the lock: `commit()` calls `beforeWrite()` itself and passes none to `write` | T1 (the revoke spy has a call) |
| M2 | The fire refusal dropped: the `:188` revision check removed | T1 (the write lands over the hand edit) |
| M3a | The mark dropped: no `revoked = true` in the catch | T2, T3, and T4 through the codes |
| M3b | `#organizationFail` ignores `revoked` | T4 |
| M3c | CLI text: the two REASONS entries removed | T5 |
| M3d | Console text: the two AGENT_REASONS entries removed | T6 |
| M3e | The notice: the `fire.failed` sentence removed | T4 |
M3a-M3e together are the brief's "drop the may already be revoked text".
## Files
- `packages/business/src/organization.mjs`
- `packages/business/src/instance-settings.mjs`
- `packages/business/tests/instance-settings.test.mjs`
- `packages/bus/src/broker.mjs`
- `packages/bus/tests/fire-revokes-grants.test.mjs`
- `packages/cli/src/instances.mjs`
- `packages/cli/src/transport.mjs`
- one cli test file
- `packages/webui/src/serve.mjs`
- `packages/webui/src/bus.mjs`
- `packages/webui/tests/agents.test.mjs`
- This packet.
## Gate
As the brief says:
- Duckworth approves the manifest.
- The bus, business, cli and webui suites and every `scripts/test-*.sh` are green on Sage's slot, with
the touched test files run whole.
- M1-M3e are killed.
Before the in-review move I grep the tests for the changed comment and error text, and for hash pins on the
changed files.
## Questions for Duckworth
- **Q1.** Should `beforeWrite` run after the writer's validations (my pick) or straight after `:188` (the
brief's literal place)? Running it later means a document the writer would refuse revokes nothing.
- **Q2.** Are two new codes, `revision-conflict-revoked` and `outcome-unknown-revoked`, the right way to
carry the signal? The alternative is to give `BusError` a detail field, which every CLI and Console
reader would then have to learn. The codes are additive and no existing code changes meaning.
- **Q3.** The brief's mutant "drop the fire refusal": I read it as removing the `:188` check (M2). If you
meant the fire test's own refusal, say so and I'll redefine it.
-16
View File
@@ -258,19 +258,3 @@ Suites (20:01:47Z):
Not run here: every scripts/test-*.sh and the other packages. Those are Sage's 283 gate, after Webby's
APPROVE.
## 2026-10-23 G1: mos-1528 OK for the mapping.test draft deepEqual (sage-3713)
This entry completes the N3 line above (BUILD.md:42, "only the draft deepEqual at mapping.test:48 gains
`supporting: []`"). BUILD.md is append-only, so :42 stays as written. Mos OK'd the change of that old
assertion in mos-1528. It is now at packages/library/tests/mapping.test.mjs:52, and commit 331653b38
changed it. Everything else in that commit's hunk is new test lines, not changed old ones.
- Old, mapping.test.mjs:48 at 45dca69f6:
`assert.deepEqual(m.universes.idea, { kind: 'draft', name: 'New World', books: [], playground: ['Drafts/New World/'] });`
- New, mapping.test.mjs:52 at b2a1f4092:
`assert.deepEqual(m.universes.idea, { kind: 'draft', name: 'New World', books: [], playground: ['Drafts/New World/'], supporting: [] });`
The line gains only `, supporting: []`; nothing else in it moved. The business.test and scope.test hunks
add tests only (sage-3713: no OK needed). The other old-test change in this row is survey.test:73
(mos-1330 item 4, 28918824c). Doc only.
-8
View File
@@ -1,8 +0,0 @@
# Lena
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 296 (FED R7, handed over from Quackerjack).
No launcher in this directory. The seat runs as a T3 thread (thread db27faca) and works in its own worktree. It has no
credentials of its own; it pushes only its own `lena/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
+277
View File
@@ -0,0 +1,277 @@
# Row 286: C2-03b push and per-seat credentials for the library mirror (#1791)
Owner Ludwig. Reviewer Dewey. Gate owner Sage (sage-2909).
Brief: `docs/plans/2026-10-20_case2-rows.md`, "## C2-03b", at 94c33a43c. Design 7.2 and the ruling
after r11 "agents never write story" in `agents/gosalyn/work/case2/DESIGN.md` at b68d79663.
Base: `refactor` at 94c33a43c. The brief cites aca16404c, and lines have moved since:
`GRANT_EVIDENCE` is `broker.mjs:253` now, not `:246`.
Status: code written, local and unrun (18:48Z on). No node until Sage's CLEAR, after row 258's run.
## What the code does today (94c33a43c)
- `packages/library/src/sync.mjs:32` `syncCommand(kind, url, {mirror, branch, user, slot})`. It refuses
any kind but `fetch`. The env passes `MOSAIC_LIBRARY_HOST`, `_USER` and `_SLOT` to `bin/askpass`.
- `bin/askpass` checks `-f` and `! -L` on the slot, then reads it. It doesn't check the uid or the mode.
- `sync.mjs:51` `slotPresent` checks lstat: a regular file, our uid, no group or other bits.
- The slot path is built in `packages/bus/src/runtime.mjs:211-217` (`syncOptions`) as
`<configDir>/secrets/<business>/library-gitea`. The broker only calls `this.#library.sync(context,
{accept})` (`broker.mjs:4630`), and the timer calls `libraryModule.sync` directly (`runtime.mjs:533`).
- The sync state file has a strict key list in `packages/library/src/mirror.mjs:222`.
- `chief-of-staff` is a definition (`STAFF_TO`, `broker.mjs:240`), not a fixed instance name.
- A seat's Gitea user is in `gitea_grants.user` and `gitea_users`. Its token file is
`<storeRoot>/<business>/<agent>/gitea` (`gitea-grant.mjs:379`). `giteaKeys.held(business, agent)`
answers the held case.
## Plan
1. `syncCommand(kind, url, {mirror, branch, user, file, commit})`. `kind` is `fetch` or `push`. Same
`SAFE` prefix, `-c` keys and env for both. A push is
`push --porcelain --no-verify --end-of-options <url> <commit>:refs/heads/<branch>`, with no `+`.
`commit` must be a full sha. The env var stays `MOSAIC_LIBRARY_SLOT` but now names whichever file
answers: a seat file, the chief's file, slot `library-gitea` or slot `library-human`.
2. `push(context, {commit, credential, retried, command})` in `sync.mjs`. git's porcelain line for
the ref decides:
- `!` with `[rejected]` and `(fetch first)` or `(non-fast-forward)` means the tip moved. Fetch once
with the same credential and return `{pushed: false, raced: true, tip}`. With `retried: true`, the
same rejection refuses `stale-base`. C2-02 owns the re-apply.
- Any other failure refuses `library-push-failed`. git's stderr isn't kept.
- A remote that fails C2-04's URL rules refuses `library-remote-invalid` before any git call.
3. `bin/askpass` gets `readSecretFile`'s checks, the same way: no symlink, the bus's uid, mode 0600.
In sh: `test -f && test ! -L && test -O`, plus a mode check through `stat -c %a`. Only the path
reaches that argv. The user check stays an exact match on the one configured user. The check and the
read happen at different times (TOCTOU). The bus owns the directories, so I take that as the
accepted limit (Q1).
4. Credential choice for the fetch, as a pure function `fetchCredential(store, business, keys)` in the
bus. The broker exposes it, and `runtime.mjs` passes its answer to `sync` for the verb and the timer:
- The chief is the one instance whose definition is `chief-of-staff`.
- Evidence: `SELECT 1 FROM events WHERE business=? AND ${GRANT_EVIDENCE} AND
json_extract(body,'$.method')='gitea' AND json_extract(body,'$.agent')=?`, of any action. It
names no project and never reads `gitea_grants`.
- No evidence: slot `library-gitea`, `{kind: 'slot'}`.
- Evidence, a live `gitea_grants` row for the chief and not held: `{kind: 'chief', user, file}`,
with the user from that row. The fallback test never reads the table. This path does, because it
needs the user.
- Evidence and anything else (revoked, fired or held): `library-fetch-no-grant`, before any file is
opened.
- No chief instance (fired): Q2.
5. The sync state gets `credential: {kind, user} | null`. `library.status` shows it.
`library-fetch-no-grant` shows as `lastError.code`. This changes `mirror.mjs`'s key check (Q3).
6. Slot `library-human` is `<configDir>/secrets/<business>/library-human`. `humanCredential(...)`
refuses `library-human-missing` before any git call when the slot is missing or fails the file checks
(lstat: regular file, our uid, 0600). Her Gitea user: Q4.
## Files owned extension (sage-2922)
Added to the brief's list: `packages/bus/src/runtime.mjs` (`syncOptions` at :211-217 and the timer's
call at :533) and `packages/library/src/mirror.mjs` (the state key list at :222). sage-2922 wrote
`packages/library/src/runtime.mjs`, but the file is `packages/bus/src/runtime.mjs`. In mirror.mjs
the row 222 contract holds: keys are added and none renamed. Before in-review I grep `packages/*/tests`
for hash pins and path reads on both files and check them against every in-review manifest.
## Scope questions (for Dewey and Sage before code)
- Q1. The askpass TOCTOU above. Is it accepted as is, or should the helper be a node script that does
O_NOFOLLOW plus fstat? If node, it would read the token in process and print it to git's pipe.
- Q2. Fired chief. The instance and its `gitea_grants` row are gone, so I can't name the agent for the
evidence check. My recommendation fails safe: with no chief instance, the fetch refuses
`library-fetch-no-grant` when any gitea grant event exists in the business, and uses the slot only
when none exists.
- Q3. Files owned names `sync.mjs`, `askpass`, `broker.mjs` and the tests. The choice also needs
`packages/bus/src/runtime.mjs` (`syncOptions` and the timer's call) and
`packages/library/src/mirror.mjs` (the state key list for `credential`). I'd add both to the
manifest, with only those hunks changed.
- Q3: approved, sage-2922. See above.
- Q4: sage-2922 allowed `user` only if design 7.2 or the ruling after r11 makes it her account. Neither
does. `packages/library/README.md:31` at 94c33a43c says "`user` is the bot account name from design
step 3, required for sync", so it is the fetch identity and can't be reused for her save. Back to Sage.
My recommendation: `humanCredential(context, {user})` takes her login as a parameter, so this row adds no
mapping field. The row that adds the save, C2-02 or C2-11a with `editor`, names the field. That
needs a mapping schema change, which is Sage's call.
Ruled sage-2925: accepted. `humanCredential(context, {user})` refuses `library-human-missing` before
any git call when the slot is missing or bad or `user` is missing. This row adds no mapping field.
Row 283 (C2-01d) adds an optional `editor.user`, and row 288 (C2-02) passes it in.
- Q4 (original). Her Gitea user for `library-human`. The entry's `user` (`mapping.mjs:52`) is today the sync
user, the bot's. The design names `editor {name, email}` (C2-11a) but no Gitea login. My
recommendation: the entry's `user` becomes her Gitea login, used only with slot `library-human`. The
fallback fetch keeps that same user too, because the bot slot was set up for it. Otherwise this row
takes the user as a parameter and C2-02 or C2-12b names the field.
## Tests (brief list, mapped)
T1 push lands a fast-forward. T2 a moved tip reports the race. T3 a second race refuses `stale-base`.
T4 a dead remote refuses `library-push-failed`. T5 askpass answers with the named seat's user and file,
and refuses another seat's user, a symlink, 0644 and another uid (with a fallback note if the test can't
chown). T6 the fetch uses the chief's file with a live grant, and the slot only with no chief event.
T7 revoked (through `grants.revoke`), fired (through `instance.fire`) and held each refuse
`library-fetch-no-grant` with a valid slot present, and each asserts the `gitea_grants` row is gone
first. T8 a chief grant on another project ends the fallback. T9 T7's three cases with the slot at 0000.
T10 a missing `library-human` refuses `library-human-missing` and runs no git. T11 a token search of
argv, the mirror config, the run log and the test output. Mutant: a `+` added to the push refspec fails.
## Gate
Dewey approves the manifest. The library and bus suites, plus every `scripts/test-*.sh`, pass on Sage's
gate. The mutant dies. A token search of the run's output finds nothing.
## Early look (dewey-0860), folded
Q1, askpass: sh, with no gap between the check and the read. The helper keeps `test -f` and then:
- opens the slot with `exec 3<"$slot"`;
- stats the open file with `stat -L -c '%d:%i %u %a %s %F' /dev/fd/3`, and the path itself (an lstat)
with `stat -c '%d:%i' -- "$slot"`;
- refuses unless the two dev:inode values match, the type is regular, the uid is `id -u`, the mode is
600 and the size is at most 16384;
- reads the token from fd 3.
A symlink or a swap at either moment changes the inode, and the token comes from the file that was
checked. This matches `readSecretFile` (`gitea-grant.mjs:167-181`).
Residual, the same on both paths: a FIFO swapped in after `test -f` blocks the open. Neither side uses
O_NONBLOCK.
Swap test T5b: the direct helper test puts a `stat` shim on its own PATH. On its first call the shim
renames another file over the slot, then runs the real stat. The helper must exit 1 and print nothing.
Mutant M2 drops the inode compare and must fail T5b.
Q2, the chief: found through `roles` by definition, not through `launch.instances`. A fire only delists
the name (`packages/business/src/instance-settings.mjs:355`), and `roles[name]` stays. Only a delete
removes it (`deletedDocument`, :70).
- Gitea grant evidence for any `roles` entry whose definition is `chief-of-staff` ends the fallback.
That includes a fired chief beside a new listed one.
- A live credential comes only from the listed chief's live, unheld `gitea_grants` row.
- With no `roles` entry of that definition (deleted, or never a chief), any gitea grant event in the
business refuses `library-fetch-no-grant`.
- Cost, for Sage: row 236 lets seat gitea grants exist before C2-21. So a business with no chief
entry and one seat grant loses the slot fallback. That fails safe, but it narrows "an install that
runs before C2-21 still syncs".
- T7's fired case also asserts that `roles[chief]` still exists after `instance.fire`.
N1: the `credential` key is optional in `mirror.mjs`'s exact `keys(...)` check (:221), so a pre-286
state file still reads. The old fixtures have no `credential`: `library-sync.test.mjs:18`,
`library.test.mjs:106` and `mirror.test.mjs:13`. New test T12 reads a pre-286 state file. Row 224
(Della, 23816cf28) also changes `packages/bus/src/runtime.mjs`. Whichever lands second rebases and reruns
bus whole.
N2: `[remote rejected]`, a hook refusal, is `library-push-failed`, not a race. Only `[rejected]` with
`(fetch first)` or `(non-fast-forward)` counts as a race. The evidence query depends on
`body.method` and `body.agent`. gitea `grant.applied` carries both at `broker.mjs:2479` and
`grant.revoked` at :1862 and :2797. The spend event (`operation: 'grant.gitea'`, :1093) carries
neither, so it doesn't match.
N3: `fetchCredential` reads the store, so it is the bus's choice function, not a pure one.
## Rulings sage-2928
- Q1: Dewey's fix, in sh. On dragon-lin `/bin/sh` is bash, and GNU coreutils stat 9.11 gives
`stat -L -c '%d:%i %u %a %s %F' /dev/fd/3` on an open fd. Checked at 18:47Z with a scratch file, not
the repo. The helper runs as git's child in the bus process, not under bwrap. T5b stages the swap with
the PATH stat shim. If a run shows the swap can't be staged reliably, BUILD.md names M2, which drops the
fd compare, and it gets killed.
- Q2: chief by `roles[*].definition === 'chief-of-staff'`. The blanket refusal applies only with no such
entry. Departure from the brief's "an install that runs before C2-21 still syncs", accepted in
sage-2928: it refuses rather than widens, and every install enrolls the chief at initial configuration
(F40, Q226). The refusal's message names the fix: "add the chief of staff, or grant the library
through C2-21".
- N1: as above.
## Build notes (code written; nothing run yet; dewey-0864 folded)
Files changed:
- `packages/library/src/sync.mjs`: `syncCommand` takes `push` with a full-sha `commit`; `push()`;
`humanCredential()`; `sync()` takes `credential`; `HUMAN_SLOT`, `CREDENTIAL_KINDS`. The `slot` opt
and `MOSAIC_LIBRARY_SLOT` keep their names and now name whichever file answers.
- `packages/library/src/index.mjs`: the new exports.
- `packages/library/bin/askpass`: Dewey's fd 3 check.
- `packages/library/src/mirror.mjs`: optional `credential` in the state key check. Nothing renamed.
- `packages/bus/src/broker.mjs`: `LIBRARY_FETCH_FIX` and `libraryFetchCredential(business)`.
- `packages/bus/src/runtime.mjs`: `syncOptions` passes the broker's choice to the verb and the timer.
The timer's held line adds `fix` for `library-fetch-no-grant`.
- `packages/library/README.md`: "Push and credentials (C2-03b)".
- Tests: `packages/library/tests/sync.test.mjs` (appended), `packages/bus/tests/library-credential.test.mjs` (new).
Choices made without a ruling. Each is small and reversible:
- `humanCredential(context, { user, configDir })`: sage-2925 named `{user}`. The slot path needs the
config dir, and the library package doesn't know it, so the caller passes it. That's the same
source runtime's `slotDir` uses.
- `BusError` carries only a code, so the sage-2928 fix text can't ride on the verb's refusal. It
goes in the timer's log line (`fix`) and in the README. The verb answers `library-fetch-no-grant`.
- T7 and T9 run the real verbs (dewey-0864, ruled by sage-2947: no SQL staging; replaces the
staged state in cefebf65b). Until row 285 lands, the test passes its own definitions; once it lands
the real chief definition carries gitea. Whichever of 285 and 286 lands second rebases and reruns
library and bus whole (sage-2947). The chief's grant goes through decision.raise and the human's yes against fake-gitea-api.mjs. The test's own
definitions give chief-of-staff a gitea credential, as gitea-grant.test.mjs gives coder one. The
real roles/chief-of-staff.json has `credentials: []`, so a live chief can't get one before C2-21.
- Revoked: `grants.revoke`, then the row is gone and the fetch refuses.
- Fired: `instance.fire` can't fire the launch.by chief. Only the launch.by role calls it, and it
refuses its own name (`organization-instance-referenced`, broker.mjs `#chiefLifecycle`). So the
test's launch.by is `boss` (definition pm, the chief's authority), and boss fires the one listed
chief-of-staff instance. Step G deletes the row (row 281), `roles.chief` stays, the fetch refuses.
Only launch.by may call instance.fire (`role-not-chief`, broker.mjs:4308 at 94c33a43c) and it
refuses its own name (:4335), so this is the only fired chief the verb allows. Accepted by
sage-2951 as brief owner and read the same way in dewey-0869.
- T9's revoked case runs `grants.revoke` on the runtime's own data root and store, between starts.
T9's runtime gets a scratch roles/ whose chief-of-staff carries `{service: gitea}`, as row 285
ships it (case2-rows.md:1823-1824), so its definitions match the grant's (dewey-0869). After a
rebase onto 285 the copy needs no edit.
- Delisted with the row still there, a `fire.stopped` with no `fire.done`, held, two chiefs and
another project stay unit checks on the choice function with SQL fixtures.
- "Another uid" in T5: the suite can't chown, so a PATH `stat` shim reports uid+1 for the open file.
The real `id -u` compare is covered only through that shim (dewey-0864).
- Two listed chiefs, or none listed, refuse `library-fetch-no-grant` (T8).
- The race fetch inside `push()` takes sync's `running` guard (dewey-0864 R1, in this row per
sage-2947). While a sync of the mirror runs it refuses `library-sync-busy`, fetches nothing, and
the caller writes again. Test: "race fetch: while a sync of the mirror runs". Its sync fetch is a
git alias that touches a marker, then waits for a release file. The push starts after the marker
exists and the release comes after the refusal, so nothing depends on timing (dewey-0869).
- The slot at 0000: T9 covers the revoked case at runtime. Fired and held refuse inside the choice
function, which takes no path and opens no file. `sync` with any `{ refused }` runs no git (library
test, valid slot present).
- First-run reds in the new tests are mine to fix in setup and report afterwards (mos-1272).
- The chief's evidence is any gitea grant event naming the chief, of any action (grant.applied,
grant.revoked and the rest), per the plan. The spend event names no agent, so it doesn't count.
Test map:
- library sync.test.mjs: argv (push), T1, T2+T3, T4 (dead remote, hook, wrong token), refused or
unsafe credential runs no git, T10, fetch credential and state with N1/T12, T5+T5b.
- library sync.test.mjs also: race fetch busy (R1).
- bus library-credential.test.mjs: T6, T7 (real revoke and fire), T7 choice checks, T8, departure,
T9 runtime (real grant, chief file and user via timer and verb; real revoke, slot 0000: no git,
log `fix`, state `credential: null`).
- webby-0151 R3: T9 reads `library.status` for `credential` and `lastError`.
- T11 token search: T1 scans the data root and every file but the seat's; T9 scans the log lines.
The gate's output search uses the fixture tokens `fixture-token-*-made-up-00{42..49}`.
Mutants (each must die on a semantic assertion):
- M1: `+` before the push refspec in `syncCommand`. Expected to fail: the argv test, and T2 (the
forced push lands, so `raced` is never reported and her commit is gone).
- M2: drop `test "$1" = "$path"` from askpass. Expected to fail: T5b (the helper prints the checked
file's token).
## r2 rebase (sage-3579)
Sage ruled that 286 now goes before 258, because 222 is its only after row. Branch ludwig/row286-r2
rebases b9506b352 (base 94c33a43c) onto origin/refactor 45dca69f6. The rebase had no conflicts.
- Range-diff 94c33a43c..b9506b352 against 45dca69f6..r2: all 11 commits are `=`, so no patch
changed.
- Rows 254 (331597709) and 268 (c27207f6e) were already in the old base 94c33a43c. Between the two
bases nothing changed under packages/library.
- Paths the two test files import or read, diffed 94c33a43c..45dca69f6: bus broker.mjs, runtime.mjs
and store.mjs changed. None of the others changed: library/*, bus credentials, gitea-grant,
mapping, mirror, reader, survey, sync, the fake-gitea-api, helpers and host-fixtures modules,
business helpers (rolesCopy, systemFor), project git, seat checkout and fake-gitea, and roles/.
- broker.mjs: the constructor gains `accessStamp` and `compactions`, both defaulting to null, and
my tests pass neither. No base hunk falls in the fire job, the revoke job or the instance.fire
verb. Old :4308/:4335 are now :4852/:4879, with the same text. The new console-cap check refuses
a non-console session only on CONSOLE_VERBS, and the library verbs are not among them. The
request path's access re-read applies only to `s.user` sessions, and my tests open none.
- runtime.mjs: consoleOwners (default []), the access timer, compactions and the session-clear
hooks. The Credentials and giteaStart wiring that T9 uses is unchanged.
- store.mjs: schema 3l to 3n (row 206 thread_shares, row 271 session_ops and message_cancels).
Each test opens a fresh Store, and none pins a schema version or step count.
- Hash pins: no test or script line pairs a 64-hex value with a path I change. The positive
control finds 64-hex pins in, for example, account-migration.test.mjs.
- Row 285 has not landed. roles/chief-of-staff.json at 45dca69f6 still names no gitea credential,
so T9 keeps its scratch roles copy (sage-2947).
- sage-3589 freezes stack-release.sh, webui-service.sh, bus-service.sh, stack-copy.sh and common.sh.
This row changes none of them.
- Still to run, on CLEAR: library and bus whole, every scripts/test-*.sh, M1 and M2, and the token
search, all on r2.
-92
View File
@@ -1,92 +0,0 @@
# Row 298 (#1804, Q228): the chief-of-staff grant scope
Owner Megavolt, reviewer Dewey. Assigned in sage-3584 (mos-1489), 2026-10-23 18:53Z.
Base: origin/refactor 45dca69f6. Branch: megavolt/row298.
Brief: docs/plans/2026-10-23_mvp-readiness-rows.md, "Brief: the chief-of-staff grant scope (Q228)".
Decision: DECISIONS Q228 (jason-master-restart DECISIONS.md:2278). It replaces Mos's Q190 reading.
## What exists at 45dca69f6 (broker.mjs)
- Raise: `#raiseGrant` (:2558) is chief-only (`grant-chief-only`) and binds a grant body per method:
gitea `#giteaGrantBinding` (:2665), host `#hostGrantBinding` (:2599), or host-enroll `enrollBinding`.
The decision goes through `#dispatch(..., 'decision.raise', ...)`, routed to the human.
- Approval: `#approvedGrant` (:1525) returns the binding only for a credential.mint routed to the
human and closed with `approvalChoice` by the business's human, via 'cli', or via 'console' with
the broker's own human.input (row 252).
- Start: decision.resolve (:5653-5659) runs `#approvedGrant` after the close and, once the transaction
commits, starts `#runEnrollment`, `#runHostGrant` or `#startGitea` with setImmediate.
- Spend and records: each executor calls `#consumeGrant` and writes the 236 records (grant.call,
grant.result, grant.step, grant.applied or grant.failed, gitea_grants or host_grants). Revoke
goes through grants.revoke and hosts.revoke, as before.
- Human-only settings: `#human(s)` (cli, outsideAgent) plus `#mayHuman`, as in veto.set (:4713).
The organization file is checked whole by business.mjs `checkLaunch`, and a bad file refuses
every read.
## Proposed design (waits on Sage's rulings, Q1-Q6 below)
D1. Storage. The scope gets its own key, `launch.grantScope`, in the organization file. The
whole-file check doesn't refuse on it. A separate reader, `grantScope(document)`, returns the
parsed scope or, when the key is missing or bad, the empty scope `{}`, plus a `fallback` flag.
A bad value can't break every organization read, and it falls back to "everything needs
Approve", which is the fail-closed direction. (Q1)
D2. Shape. `{ gitea?: { agents: [id], projects: [id] }, host?: { agents: [id], hosts: [id],
commands: [[argv prefix]] } }`. A request is in scope only if its method has an entry, its agent
is listed, and every target is listed: each gitea project, the host, and each command prefix
equal to a listed prefix. host-enroll is never in scope, because it adds a host to the registry.
(Q2)
D3. Writer. A new verb, `grants.scope.set { revision, scope }`, CLI-only for the human: `#human(s)`,
then `#mayHuman`, with the verb added to LIFECYCLE_HUMAN_VERBS. The tester, the reader and every
agent are refused. organization.set's ORGANIZATION_KEYS don't include it, so the chief's tool
never reaches it, the same way instances.set and veto.set stay human-only. The write validates
strictly and refuses a bad value; only the read falls back. Receipt: `grants.scope` with the old
and new sha256. `grants.scope` reads the value for the human, the tester and the chief. (Q3)
D4. In-scope raise. Inside the raise transaction, `#raiseGrant` checks the binding against the scope
and, if it's in scope, closes the new decision at once: op resolved, choice approvalChoice, by
`grant-scope`, via `scope`. It writes a `grant.scope.used` event carrying the scope sha256 and
revision. `#approvedGrant` accepts via 'scope' only when that event exists for the close's seq
and its sha256 matches the scope as read in the same transaction. After the commit, the same
setImmediate starts the same executor, so 236's spend, records and revoke are unchanged. The
decision is closed before any inbox read, so no inbox item ever shows. (Q4)
D5. Out-of-scope raise: unchanged. The decision stays open, routed to the human, for the Console
and CLI Approve.
D6. Executor recheck. At the spend, `#consumeGrant` reruns the scope check for a via-'scope'
approval. If the scope changed between raise and run and the grant is now out of scope, it
fails with `grant.failed scope-changed` and the grant is spent. (Q5)
## Open questions for Sage
Q1. Storage: `launch.grantScope` in the organization file with a lenient reader (D1), or a separate
file under the business directory?
Q2. Shape: is per-method agent, project, host and command lists right? Should host-enroll stay
out of scope always?
Q3. "A scope change needs Approve": is the human's CLI verb itself the Approve (D3)? Or must the
chief also be able to raise a scope change that Jason approves in the inbox (a new decision
action, more code)? I recommend D3 alone. Should Console get a scope page in this row? I
propose no.
Q4. Recording an in-scope grant as a broker close via 'scope' with a scope event (D4), versus a
decision that is never raised (a new table). D4 keeps 236's records and revoke unchanged.
Q5. Scope narrowed between raise and run: fail scope-changed (D6), or honor the raise-time check?
Q6. Chief enrollment ("Jason's Approve stays for the chief of staff's enrollment"): I read this
as the chief's own instance and credentials, which the human's instances.set and the
credential lifecycle already cover. So nothing new is needed in this row. Agree?
## Tests (access by brief; all on scratch stores, fake Gitea, fake host)
T1 agent, chief and tester callers of grants.scope.set are refused, and organization.set with
grantScope is refused. T2 a bad or missing grantScope falls back to empty and the grant waits.
T3 an in-scope gitea request issues with no open decision and no inbox item, and writes the 236
records. T4 the same for a host request. T5 out of scope by agent, by project, or by command
waits for Approve. T6 host-enroll always waits. T7 a scope change by the human's CLI writes a
receipt; a scope change by any other path is refused. T8 an in-scope grant is logged and revoked
with grants.revoke or hosts.revoke. T9 scope narrowed before run gives scope-changed.
## Mutants (brief)
M1 the scope check skipped (every chief raise auto-closes): killed by T5 and T6.
M2 the fallback skipped (a bad value is used as-is or throws): killed by T2.
M3 the `#human` check removed from grants.scope.set: killed by T1.
## Holds
Darkwing holds the slot: no push, no claim, no queue write, no node and no test run until Sage
says so. Q188: no agent creates a grant; tests use scratch stores only.
-10
View File
@@ -1,10 +0,0 @@
# Poe
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1566. Owns row 208, 193d: the Console's shared
list, share controls and viewer read-only (docs/plans/2026-10-20_human-users-build.md), reassigned from Morgana so the
coding runs on Codex.
No launcher in this directory. The seat runs as a T3 thread and works in its own worktree. It has no credentials of its
own; it pushes only its own `poe/*` branches through the repository credential helper (mos-0981), never `refactor`,
`main` or another seat's branch. Claude seats orchestrate and review guards (mos-1542 rule 4). Registered by Sage on
2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
-53
View File
@@ -182,56 +182,3 @@ has been thrown; the kill claims are unverified until CLEAR):
Built, not run. `node --check` has not been run on this round's files (no node before the CLEAR); the
phase A pass above covered the earlier text only. The commit is local and waits for no gate under Sage's
sage-2716 instruction, which allows git and docs work only; push and claim wait for Sage's go.
## Round 3: rebase onto 45dca69f6, drift check, hash-pin grep (2026-10-23T18:53:43Z, sage-3576)
Appended; nothing above is edited. Git only; nothing run, nothing pushed. Branch researcher/row216-r4 holds the
one row 216 commit on 45dca69f6 (origin/refactor, 10-23). Earlier base: 48e450ce8 (row 253).
- Rebase: clean. `git range-diff 48e450ce8..038089e5f 45dca69f6..researcher/row216-r4` shows the commit as `=`.
- Landed in 48e450ce8..45dca69f6: 1175 files. Path overlap with this row's 7 paths: none. packages/library: no
landed file. packages/harness/src/tools.mjs (imported by query-fixture.mjs or the tests): not landed.
- Hash-pin grep (the item 216 owed): 64-hex in packages/library: none. Positive control: 364 files in
packages/*/tests and scripts match 64-hex. Tests and scripts that name library files by path:
bus/tests/library.test.mjs and library-sync.test.mjs and bus/src/runtime.mjs import library/src/index.mjs;
business/src/business.mjs and its test import library/src/mapping.mjs (unchanged by this row);
cli/tests/stack-copy.test.mjs, stack-release-fixture.mjs:310 and stack-release.test.mjs:1991 copy
packages/library/src as a tree; scripts/stack-copy.sh:37 copies it; scripts/mosaic:41 runs survey.mjs. None pins
a hash of index.mjs, index-build.mjs or query.mjs.
- bus/src/runtime.mjs landed changes after 48e450ce8 and uses libraryModule by named members (ensureMirror, SLOT,
readLibraryFile, libraryStatus, sync, LibraryError, SYNC_INTERVAL_MS) only; the landed diff touches none of them,
and this row's two new index.mjs exports (query and index build) are not iterated by any caller.
- Gate additions for phase B: run bus library.test.mjs and library-sync.test.mjs whole (they import index.mjs), and
the cli stack-copy and stack-release tests that copy packages/library/src.
## Round 4: phase B on 4e5d28ab2, two test fixes for r5 (2026-10-23T20:20:51Z, sage-3702, mos-1528)
Appended; nothing above is edited.
Phase B receipts on 4e5d28ab2 (researcher-r216-kit/out-b1): library whole 114 pass, 2 fail; bus library.test and
library-sync.test 12 pass; cli 266 pass. The 3,000-file rebuild took 3807 ms (the test, with fixture setup, 4070 ms;
`t.diagnostic` in index-build.test.mjs). No row 216 mutant was required or thrown in that round.
The 2 library reds are test faults, not product faults. Both are placed as first-run reds in files this row adds
(mos-1272); Mos ruled both fixes in mos-1528 and Sage relayed it in sage-3712.
- R4: `packages/library/tests/index-build.test.mjs`, test "a different repo path, mapping or commit gets its own
index". Old: `writeFileSync(join(repo, 'Book One/Places/New.md'), …)` with no `Book One/Places` folder in FILES, so
the write failed with ENOENT. New: one `mkdirSync(join(repo, 'Book One/Places'), { recursive: true })` before the
write (import line gains `mkdirSync`). No assertion changes.
- R5: `packages/library/tests/query.test.mjs`, test "a hybrid sees its sources canon, minus exclusions, and no
playground". The assertion sorts the actual paths and compared them to an unsorted literal, so the literal had
the wrong order (Characters before Chapters; Second World Characters before Chapters). New: only the expected
literal is reordered into sorted order. The `.sort()` on the actual side stays, and the assertion still checks
order. The set of seven paths is unchanged.
- Product code: no edit.
Reruns owed after the r5 commit (run on Sage's CLEAR, after the 283 gate): index-build.test.mjs whole,
query.test.mjs whole, library whole, and the mutants those two files kill (M-a to M-e above). Results are appended
below as a new entry.
Run limit note: stack-release.test.mjs ran 644 s in the cli run. A per-file limit of 3 x 644 = 1932 s covers it.
Sage's gates run cli as one whole-package `node --test` under 2100 s.
Carry notes from Filbert's static check (doc only, no change in r5): N1 and N2 stay recorded as he gave them. The
`After` list in this file still names row 268; row 268 landed at c27207f6e.
-100
View File
@@ -1,100 +0,0 @@
# Row 220: C2-10a, no raw session view (M7) and no raw Discord engine (M8)
Phase A. No test, suite or mutant has been run. Sage opens the slot.
- Owner: researcher seat (Haiku 5.5 subagent), dispatched by Sage under lead decision 129.
- Reviewer: Filbert.
- Branch: `researcher/row220`, from `origin/refactor` at d37210288.
- Brief: `docs/plans/2026-10-20_case2-rows.md`, C2-10a (line 495) and "Rules every row keeps".
- Design: `agents/gosalyn/work/case2/DESIGN.md` on `origin/gosalyn/case2-design`, sections 5.1 (M7, M8), 7.6, 8 (row list).
## Status
| Item | State |
|---|---|
| M7 reader side (`conversation-not-listed`) | Built. Tests written, not run. |
| M7 production wiring (the caller that passes `isCreative`) | Not built. Outside the file list, and it needs a ruling (question 1). |
| M8 `discord check` and `discord run` refusals | Not built. `packages/discord/src/cli.mjs` is untouched. Blocked on question 1 and question 2. |
Phase A is therefore partial. The row cannot pass its acceptance until Sage rules on the questions below.
## Files
- `packages/conversation/src/reader.mjs`: exports `NOT_LISTED`; `createReader` takes `isCreative` (default `() => false`); `catalogue()`, `open()` and `next()` hide a root for which it returns truthy.
- `packages/conversation/tests/reader.test.mjs`: three new tests, inserted before the "unknown conversations" test.
- `agents/researcher/work/queue-220/BUILD.md`: this file.
Nothing else is changed. `node_modules` in the worktree is a symlink to the canonical checkout and is ignored by git.
## Brief and design items mapped to tests
| Item | Test |
|---|---|
| M7: a creative session file under the root isn't listed | "M7: a creative seat's session is not listed and won't open by id; an assistant seat still lists" |
| M7: and won't open by id (`conversation-not-listed`) | same test; second check of the open refusal in "M7: a creative session already open refuses its next page once the root is hidden" |
| M7: an assistant session still lists | same test (the `sage` seat is listed and opens with its text) |
| M7: an already-issued cursor stops once the root is hidden (my addition; the design's "won't open" implies it) | "M7: a creative session already open refuses its next page once the root is hidden" |
| M7: refused rows and the unsupported placeholder of a creative root are hidden too (my addition; the design hides "creative sessions", and a refused row or placeholder names one) | "M7: a creative root's refused files and its unsupported placeholder are hidden as well" |
| Default behavior without `isCreative` is unchanged | the existing reader suite; the default predicate returns false |
| M8: `discord check` / `discord run` refuse `discord-engine-not-allowed` on a library business, and a `mosaic-bus` binding with `tools` | none (not built, question 1) |
| M8: a pi binding on a business without a library still checks clean | none (not built, question 1) |
Every test's fixture text is `CREATIVE-MARKER` or a made-up assistant line. The M7 tests assert the marker is absent from the catalogue and refusal output.
## Choices made in phase A
- `isCreative(root)` is the reader's only input. The reader reads no business file. A truthy answer hides, so a doubtful answer fails closed. The predicate must depend on `root.seat` and `root.project` only, because a cursor is checked against the root object from an earlier listing.
- Hidden check placed after the cursor's actor/purpose/conversation/branch checks in `next()`, and before the source is read.
- `open()` checks hidden after the id resolves, so a creative id gets `conversation-not-listed`, and an unknown id still gets `unknown-conversation`.
- `conversation-not-listed` is `reconcile: false`. The design names no reconcile flag. `unknown-conversation` uses true. Question 3.
- The default predicate is `() => false`. Until a caller passes one, M7 does nothing. This is the open risk, and question 1 covers it.
## Grep over `packages/*/tests` and `scripts/test-*.sh` (static, phase A)
Changed path `packages/conversation/src/reader.mjs`:
- `packages/conversation/tests/reader.test.mjs:14` imports `rootsFromSpecs, createReader, conversationId, cwdInProject, UNSUPPORTED_HARNESS`. It is the file under change.
- `packages/control-board/tests/serve.test.mjs:26` imports `UNSUPPORTED_HARNESS`. That export is unchanged.
- `packages/library/tests/sync.test.mjs:15` imports `packages/library/src/reader.mjs`, a different file. Not a hit.
- `scripts/test-*.sh`: no reference to the conversation reader.
- 64-hex pins naming this path: none. `packages/bus/tests/grant-migration.test.mjs:24` has a 64-hex value, but it is a DDL digest, not a pin on this path. That file matched only the word "reader" in a test title.
Changed path `packages/conversation/tests/reader.test.mjs`:
- No other test or script names this file. It runs through `packages/conversation`'s `node --test tests/`.
- No 64-hex value in the file.
Changed path `agents/researcher/work/queue-220/BUILD.md`: no pin.
`node --check` passed on both changed `.mjs` files.
## Phase B plan (not run)
The reader test uses `os.tmpdir()`. Set `TMPDIR` to a scratch dir first, per the rule against `/tmp`.
```
export TMPDIR="$(mktemp -d /mnt/storage/scratch/tmp/r220XXXXXX)"
cd packages/conversation && node --test tests/reader.test.mjs
cd packages/conversation && node --test tests/
cd packages/discord && node --test tests/
cd packages/control-board && node --test tests/serve.test.mjs
```
The first command is the touched file whole. The second and third are the conversation and discord suites whole, as the brief asks. The control-board serve test is not in the brief. It consumes `reader.mjs`, so I recommend it. Sage can drop it.
## Questions for Sage
1. **Business link (blocks M8 and the M7 wiring).** Neither rule can be built from this row's file list, because the inputs don't exist in the code:
- A reader root carries `seat` (the spec's `agent`) and `project` (the repo directory name). It has no business id. Roots come from control-board scan specs (`packages/control-board/src/scan.mjs`, `discoverRepoAgents`) and seat registrations. Neither carries a definition.
- A Discord binding has `seat` and no business key (`packages/discord/src/binding.mjs`, `TOP_KEYS`).
- Creative status lives only in the business file: `roles.<instance>.definition` in `CREATIVE_DEFINITIONS` (`packages/business/src/vocabulary.mjs:176`). A library project is `projects.<id>.kind === "library"` (`packages/business/src/business.mjs:243`).
- Creative thread sessions are stored at `<dataRoot>/threads/<business>/<thread>/session` (`packages/seat/src/session.mjs:59-64`), outside the `.pi/state` roots the board scans. M7 may therefore reach only creative seats that have a `.pi/state` session root. Please confirm.
- The design does not define the mapping from a seat to a business.
Recommendation: add a required `business` key to the binding, validated against `businesses/<id>.json`. The board builds `isCreative` from `loadBusiness(id)`: a root is creative when its seat is a `roles` key whose definition is in `CREATIVE_DEFINITIONS`, and the business has a library project. This needs file scope for `packages/discord/src/binding.mjs` and `cli.mjs` (M8), and `packages/control-board/src/serve.mjs` (M7 wiring). None of those is on this row's list. I need that scope amendment and the mapping ruling before I build M8 or the wiring.
2. **M8 refusal code for a `mosaic-bus` binding with `tools`.** The design names `discord-engine-not-allowed` for the engine case and no code for the tools case. A new code goes to you first. My proposal is to reuse `discord-engine-not-allowed` with a message that names the tools key.
3. **`reconcile` on `conversation-not-listed`.** I chose `false`. Confirm or change.
4. **HTTP status for `conversation-not-listed`.** `REFUSAL_STATUS` in `packages/control-board/src/serve.mjs` has no entry, so the route returns 422. My proposal is 404, next to `unknown-conversation`. This needs that file on the scope list too.
Phase A stops here. On your "slot clear" I will run the phase B commands above. I will not build M8 or the wiring until question 1 is answered.
@@ -1,481 +0,0 @@
# Row 220 r2: phase A round 2 (M7, M8, business helper, REFUSAL_STATUS)
Phase A only. No node, npm, `node --check` or test run has happened in this round, and nothing is
committed. Phase B (the suites) waits for the coordinator's "slot clear".
- Owner: researcher seat (Haiku 5.5 subagent), dispatched by Sage under lead decision 129.
- Reviewer: Filbert.
- Branch: `researcher/row220-r2`, from `origin/refactor` at da9555ca2. The r1 cherry-pick 3fa833715 is
staged with `-n`.
- Rulings: `agents/sage/work/queue-220/RULINGS.md` on origin/refactor (da9555ca2). Import-by-path is
followed: no export line in `packages/business/src/index.mjs`, and `packages/discord/src/binding.mjs`
is untouched.
## Coordinator rulings, applied
Round 1 answers (earlier packet): applied as before. Absent `businesses/` gives empty sets only on
ENOENT; other failures throw. stack-copy test joins phase B. Webui fixtures pass `businessDir` (renamed `configDirectory` in round 2b below). Discord
refusals exit 2.
Round 2 rulings (coordinator message, this round):
1. **Dangling symlink at `businesses/` refuses.** `loadSeatSets` calls `lstatSync(join(dir, "businesses"))`
first. Only an lstat ENOENT (nothing at the path) returns empty sets. If something is there and
`readdirSync` fails for any reason, including ENOENT through a dangling link, it throws `BusinessError`.
Test added: "a businesses symlink to a missing target throws; no empty set". The absent-directory test is kept.
2. **Sets are read once per public reader call.** `createReader` takes `creativeCheck`, a factory. Each
`catalogue()`, `open()` and `next()` calls it once, then applies the returned predicate to every root.
A load failure refuses `conversation-business-unreadable` for the whole call. `serve.mjs` exports
`creativeCheck(dir = configDir())`, the factory, and `createServer` passes `creativeCheck(businessDir)`.
Test added: "one catalogue calls the creative check once, however many roots it lists". It counts factory
calls: 1 after `catalogue()`, 2 after one `open()`.
3. **Phase B adds three test files:** `packages/webui/tests/tester-browser.test.mjs`,
`tester-cli.test.mjs` and `proof-cli.test.mjs`, run whole (they reach `fixture.mjs` through
`tester-fixture.mjs`). Phase B is 36 files: 33 plus these 3.
4. **Scope additions approved** (coordinator round 2 ruling 4): `packages/control-board/tests/discord.test.mjs`
line 123 and the five webui test files (`fixture.mjs`, `history-fixture.mjs`, `relaunch.test.mjs`,
`return-flow.test.mjs`, `discord.test.mjs`). They stop tests reading the real config directory.
5. **TMPDIR** for phase B is set by the coordinator under `/mnt/storage/scratch/tmp`. No code change.
## Discord exit code
Refusals from the library-seat check exit 2, the code the Discord CLI already uses for configuration and
binding refusals.
- `packages/discord/src/errors.mjs` line 2: `// exitCode follows docs/TOOLS.md: 1 operation failed, 2 invalid data or configuration, 3 refused by a brake (STOP or a held binding; a supervisor must not retry), 4 usage.`
- `packages/discord/src/errors.mjs` line 5: `constructor(message, exitCode = 2, details = undefined)`.
- Config refusal precedent: `packages/discord/src/binding.mjs` line 67, `throw new DiscordError(\`config.dataRoot must be an absolute path: ${path}\`);`. No explicit code, so exit 2.
- `checkLibraryEngine` in `packages/discord/src/cli.mjs` lines 115 to 122. Each refusal passes `, 2)`:
`discord-business-unreadable` (115), `discord-engine-not-allowed` for a non-mosaic-bus engine (119),
and `discord-engine-not-allowed` for a mosaic-bus binding that declares `tools` (122).
- `grep -n ", 3)" packages/discord/src/cli.mjs` finds nothing. `grep -rn "status, 3" packages/discord/tests`
finds only existing brake tests in recover.test.mjs and binding.test.mjs. Not changed.
`library-seat.test.mjs` asserts 2 on all three refusals.
## Files (round 2, uncommitted)
| File | Change |
|---|---|
| `packages/business/src/library-seats.mjs` | new: `loadSeatSets({ dir, rolesDir })` returns `{ librarySeats, creativeSeats }`; lstat-first (ruling 1); read per call |
| `packages/business/tests/library-seats.test.mjs` | new: 7 tests (6 from r1 plus the dangling symlink) |
| `packages/conversation/src/reader.mjs` | r1 plus r2: `NOT_LISTED`; `creativeCheck` factory option, called once per `catalogue()`, `open()` and `next()`; `catalogue()` wrapped so a refusal returns `{ ok: false }` |
| `packages/conversation/tests/reader.test.mjs` | r1: 3 M7 tests, now on `creativeCheck`; r2: 1 spy test (one factory call per catalogue) |
| `packages/control-board/src/serve.mjs` | `REFUSAL_STATUS`: `conversation-not-listed` 404, `conversation-business-unreadable` 503; `export creativeCheck(dir = configDir())` (factory); `createServer({ businessDir = configDir() })` wires `creativeCheck: creativeCheck(businessDir)` |
| `packages/control-board/tests/creative-hidden.test.mjs` | new: 4 tests (3 from r1 plus known-and-unknown id both 503), switched to `creativeCheck` |
| `packages/control-board/tests/serve.test.mjs` | pin test adds `NOT_LISTED` and `conversation-business-unreadable`; conversation fixture passes `businessDir` |
| `packages/control-board/tests/discord.test.mjs` | line 123 passes `businessDir` (ruling 4) |
| `packages/discord/src/cli.mjs` | `checkLibraryEngine` called from `prepare()`; exit 2 |
| `packages/discord/tests/helpers.mjs` | `makeDeployment` creates an empty `businesses/` beside `config.json` |
| `packages/discord/tests/library-seat.test.mjs` | new: 5 tests, all `--offline`, all asserting exit 2 |
| `packages/webui/tests/fixture.mjs` | `businessDir` on the board start (ruling 4) |
| `packages/webui/tests/history-fixture.mjs` | `businessDir` on the board start (ruling 4) |
| `packages/webui/tests/relaunch.test.mjs` | `businessDir` on the board start (ruling 4) |
| `packages/webui/tests/return-flow.test.mjs` | `businessDir` on the board start (ruling 4) |
| `packages/webui/tests/discord.test.mjs` | `businessDir` on the board start (ruling 4) |
Not touched: `packages/business/src/index.mjs`, `packages/discord/src/binding.mjs`, `scripts/stack-copy.sh`.
## Brief item to test map
| Brief item | Test |
|---|---|
| Q1 helper: library and creative sets from business files | `business/tests/library-seats.test.mjs` "library seats come from library businesses..." |
| Q1 a non-library business puts no seat in either set | `library-seats.test.mjs` "a business with no library project..." |
| Q1 absent businesses directory gives empty sets | `library-seats.test.mjs` "an absent businesses directory gives empty sets" |
| Q1 any other failure throws, no empty set | `library-seats.test.mjs` "a businesses path that is not a directory throws"; "a businesses symlink to a missing target throws"; "a business file that can't be read throws"; "a business file that is not valid JSON throws" |
| Round 2 ruling 2: one read per public call | `conversation/tests/reader.test.mjs` "one catalogue calls the creative check once..." |
| M7 hidden from catalogue, open and next | `control-board/tests/creative-hidden.test.mjs` "the check hides the creative seat..."; r1 reader tests (catalogue, open, next, symlink and placeholder) |
| M7 second wall: thread store | `creative-hidden.test.mjs` "a thread store is not a root from a spec..." (asserts `rootsFromSpecs` drops it, then a root over it is hidden with 404 `conversation-not-listed`) |
| M7 unreadable business refuses listing and open | `creative-hidden.test.mjs` "an unreadable business file refuses the listing and open with 503" |
| Coordinator follow-up: known and unknown id both 503 when unreadable | `creative-hidden.test.mjs` "with an unreadable business file, a known and an unknown id both refuse with 503" |
| M8 non-mosaic-bus engine on a library seat | `discord/tests/library-seat.test.mjs` "a library seat's binding on a non-mosaic-bus engine is refused" (exit 2, message names the engine) |
| M8 mosaic-bus binding that declares tools | `library-seat.test.mjs` "...declares tools is refused, and the message names tools" (exit 2) |
| M8 mosaic-bus, no tools, allowed | `library-seat.test.mjs` "...with no tools passes the engine rule" |
| M8 unreadable business file | `library-seat.test.mjs` "an unreadable business file refuses check with discord-business-unreadable" (exit 2) |
| M8 seat in no library business checks clean | `library-seat.test.mjs` "a binding on a seat that is in no library business checks clean" |
| Q3 reconcile false | r1 reader test asserts `[false, "conversation-not-listed", false]` |
| Q4 HTTP 404 and 503 | `creative-hidden.test.mjs` (status asserted); `serve.test.mjs` pin test |
## Choices (for Filbert to check)
1. **Absent versus unreadable `businesses/`.** Only nothing at the path reads as empty (lstat ENOENT). A
dangling symlink, a file, or an unreadable directory throws. Coordinator ruling 1.
2. **Sets are read once per public call.** `creativeCheck()` runs once in each of `catalogue()`, `open()` and
`next()`. The returned predicate answers every root in that call. A catalogue with zero roots still
loads the sets once, so an unreadable business file refuses even an empty list. Coordinator ruling 2.
3. **Behavior change, kept on coordinator ruling (round 2 follow-up): an unknown id or cursor loads the sets
first.** In `open()`, the check runs after `checkCaller` and before `resolveConversation`. In `next()`, it
runs at the start of the try. So with an unreadable business file, a known id and an unknown id both give
503 `conversation-business-unreadable`, where round 1 gave 404 `unknown-conversation` for the unknown id.
Checking the id first would give 503 for a hidden creative id and 404 for an unknown one, which reveals
whether the hidden id exists. Loading first answers every id the same way (fail closed). This reverses
round 1 choice 3. Test: `creative-hidden.test.mjs` "with an unreadable business file, a known and an unknown id both refuse with 503".
4. **Discord refusals use the `code: message` form and exit 2.** Exit 3 is for brake refusals (STOP or a held
binding). A seat's engine or library membership is not a brake. The business CLI is not used as precedent.
5. **The thread-store wall exists in part already.** `rootsFromSpecs` (`reader.mjs` line 44) drops any
sessions directory that is not `<project>/.pi/state/<seat>/sessions`. The M7 check is the second wall, for
roots built another way. The test checks both.
## Static checks (read-only; no node)
- **COPY_PATHS** (`scripts/stack-copy.sh` 29 to 41): `packages/business/src`, `packages/conversation/src`,
`packages/discord/src` and `packages/library/src` are present. `packages/control-board/src` is not. COPY_PATHS
is not changed.
- **Stale names.** `grep -rn -E "isCreative|creativeRootPredicate" packages scripts` finds nothing after this round's edits.
- **Board starts without `businessDir`.** Webui: all five direct starts pass it. Control board:
- `serve.test.mjs` has seven single-line starts without it (lines 402, 445, 464, 483, 509, 528, 907) and four
multi-line starts without it (114, 149, 229, 258). None of those tests reaches a conversation route.
Every conversation route call in the file (lines 1027 to 1171) follows a start that passes `businessDir`
(1006, 1075, 1124). Checked by line order.
- `creative-hidden.test.mjs` builds `creativeCheck(f.dir)` on its own scratch directory. No default is used.
- `discord.test.mjs` passes `businessDir` (line 123).
- **Consumers of the two fixtures** (exact import strings, `from './fixture.mjs'`, `from './history-fixture.mjs'`):
- Direct `fixture.mjs` importers, 31 test files: board-held, browser-edge, browser, bus-browser, bus,
chat-browser, chat, discord, docs-browser, editor-browser, editor, instances-browser, limits-browser,
limits-route, messages, preview, proof, relaunch, return-flow, routes-browser, send-browser, send, serve,
settings-form, shell, sso-browser, sso, tester-listener, usage-browser, usage, views.
- `history-fixture.mjs` importers, 2 test files: conversation, history-return-flow.
- Total fixture consumers: 33. Plus `tester-browser`, `tester-cli` and `proof-cli` (through
`tester-fixture.mjs`, ruling 3): 36 files in phase B.
## Phase B commands (after "slot clear" only)
TMPDIR is set by the coordinator under `/mnt/storage/scratch/tmp`. The commands below assume it. The new
tests call `tmpdir()`, so an unset TMPDIR would put them under `/tmp`, which bwrap hides.
```sh
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e/packages/conversation && node --test tests/
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e/packages/discord && node --test tests/
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e/packages/business && node --test tests/
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e/packages/control-board && node --test tests/
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e && scripts/test-discord.sh
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e/packages/cli && node --test tests/stack-copy.test.mjs
cd /mnt/storage/src/mosaic-stack/.claude/worktrees/agent-a1b36cb7769b3d00e/packages/webui && node --test tests/board-held.test.mjs tests/browser-edge.test.mjs tests/browser.test.mjs tests/bus-browser.test.mjs tests/bus.test.mjs tests/chat-browser.test.mjs tests/chat.test.mjs tests/conversation.test.mjs tests/discord.test.mjs tests/docs-browser.test.mjs tests/editor-browser.test.mjs tests/editor.test.mjs tests/history-return-flow.test.mjs tests/instances-browser.test.mjs tests/limits-browser.test.mjs tests/limits-route.test.mjs tests/messages.test.mjs tests/preview.test.mjs tests/proof.test.mjs tests/relaunch.test.mjs tests/return-flow.test.mjs tests/routes-browser.test.mjs tests/send-browser.test.mjs tests/send.test.mjs tests/serve.test.mjs tests/settings-form.test.mjs tests/shell.test.mjs tests/sso-browser.test.mjs tests/sso.test.mjs tests/tester-listener.test.mjs tests/usage-browser.test.mjs tests/usage.test.mjs tests/views.test.mjs tests/tester-browser.test.mjs tests/tester-cli.test.mjs tests/proof-cli.test.mjs
```
The webui line is 36 files, run whole. `history-fixture.mjs` and `tester-fixture.mjs` run through their
consumers. No test has been run. The expectations above come from reading the code.
## Open items
a. Phase B checks the dangling-symlink test on this platform (lstat then readdir ENOENT must throw).
b. Phase B checks the spy count (1 after catalogue, 2 after one open). A failure here means a public call
reads the factory more than once.
c. Choice 3 is settled by the coordinator: keep 503 for unknown ids too. Phase B checks the new known-and-unknown test.
d. The browser-based webui tests need a browser in this environment. A failure there would be environmental.
Phase B will show which.
e. Nothing here has been executed. Phase B checks every expectation, including the unreadable-file test
(EACCES must throw), the root skip (uid 0 skips), and the webui consumers.
## Resolved
- Dangling symlink at `businesses/` (round 1 open item a, and round 1 question 1): refuses, ruling 1.
- Tester and proof CLI tests (round 1 question 2): join phase B, ruling 3.
- Per-root reads (round 1 open item b): replaced by one read per public call, ruling 2.
## Rebase onto aca16404c, run limits, index export (2026-10-22, sage-2708)
Added after the text above; nothing above is edited. "Nothing is committed" above was true when written.
- Commit `6f656abac` on `researcher/row220-r2`, one commit, rebased from da9555ca2 onto origin/refactor
`aca16404c`. Row 253 has not landed; this rebases again onto 253's tip before any gate.
- Drift check, da9555ca2..aca16404c against my 18 paths: one overlap, `packages/discord/src/cli.mjs`
(the processing-control imports, rows 264 and neighbours). The conflict was the import block only. I
kept both sides: the two landed `processing-control` imports and my `loadSeatSets` import. No landed hunk
is reverted. `checkLibraryEngine` and its call in `prepare()` merged without a marker.
- Board starts: no test added since da9555ca2 starts the control board or reads a conversation route
without `businessDir`. `packages/webui/tests/shell.test.mjs` fakes the board and does not start it.
- Two edits since the r2 text: (1) a missing space in `reader.mjs` `next()` (`held =typeof`) fixed;
(2) `packages/business/src/index.mjs` gets `export { loadSeatSets } from "./library-seats.mjs";`. The text
above says "no export line". RULINGS.md Q1 says the helper is "exported from the package index" and the
scope amendment lists "its export line", so the file wins and the export is added. The two callers still
import by path, so nothing needs the export today. Filbert: drop the line if you read the ruling otherwise.
The export adds no test; `node --check` has not been run on it (no node before the CLEAR).
### Run limits (fixed before any run)
Every node file runs as its own `setsid -w timeout -k 10 LIMIT node --test FILE`; nothing runs unbounded.
- Node test files: LIMIT 900 s. I found no recorded per-file time for the conversation, discord, business,
control-board or webui files. 900 s is above the longest recorded whole-package run I know of (cli, 591 s,
row 281 BUILD.md "Runs" step 3), so it holds the 3x rule for any single file. The first run records each
file's time; if a file passes 300 s, the limit is re-derived from it before the next run.
- Shell scripts: `timeout -k 10 LIMIT bash scripts/test-X.sh`, LIMIT = 3x the longest recorded run plus
120 s, taken from Megavolt's row 281 table (`agents/megavolt/work/queue-281/BUILD.md`, from Gander's
`agents/gander/work/queue-278/SHELL-LIMITS.json`). A script with no limit does not run.
| Script | LIMIT | Script | LIMIT |
|---|---|---|---|
| test-auth | 126 s | test-land-work | 158 s |
| test-conductor | 145 s | test-queue | 191 s |
| test-config | 130 s | test-release | 157 s |
| test-discord | 147 s | test-task | 231 s |
| test-extension-package | 130 s | test-foundation | 240 s |
Any timeout goes to Sage, then Mos, unplaced (mos-1275).
## Round 2b: Filbert's early look and sage-2719 (2026-10-22T14:46Z)
Added after the text above; nothing above is edited. Where earlier text says `businessDir`, the parameter is
now `configDirectory` (below). Second commit on `researcher/row220-r2`, on top of ea6dc9eff. Not pushed, not
run; `node --check` has not been run on any of these files (no node before the CLEAR).
Rulings applied (sage-2719): Q1 keep the index export. C1, C2 and the fixture scope are changes.
### Fixture scope: the parameter is a config directory
`createServer` took `businessDir`, but `creativeCheck(dir)` reads `<dir>/businesses`, so every fixture that
passed `join(root, 'businesses')` made the reader look in `<root>/businesses/businesses`. That read nothing
(ENOENT, empty sets): isolation from the real config held by accident. Now:
- `createServer({ configDirectory = configDir() })` in `serve.mjs`. The parameter is not named `configDir`
because the import of that name is the default's source.
- Every fixture passes the config directory it owns: `configDirectory: root` in the five webui files,
`f.root` in `control-board/tests/discord.test.mjs`, and `f.configDirectory` in `serve.test.mjs`.
`conversationFixture()` and `replyFixture()` both return `configDirectory: root`. The seven reply starts
that passed `f.businessDir` (undefined, falling back to the real `configDir()`) now pass a real value.
- The dead `mkdirSync(join(root, 'businesses'))` lines in the five webui fixtures and in `serve.test.mjs`
are removed. So is the one in `discord/tests/helpers.mjs` `makeDeployment`, whose comment said "a missing
one refuses"; that was false (lstat ENOENT gives empty sets). The comment now says nothing there means no
library seat. Tests that need a `businesses/` directory create it: `writeJson` does, and the two
unreadable-file tests in `library-seat.test.mjs` call `mkdirSync` themselves.
- Proof that the fixture's own business file is the one read: the new default-reader test (C2) hides `bean`
and shows `pm`, and nothing else in that fixture says which is which.
### C1: M8 on `run`
`library-seat.test.mjs` gains three `run` tests: a raw engine, a mosaic-bus binding with `tools`, and an
unreadable business file. Each asserts exit 2 and the code in stderr, and that `s.d.journalDir` does not
exist (`ensureJournal` comes after `prepare()` in `run`, so nothing was started). `run()` calls
`initializeControl(dataRoot)` first, because `run` stops at `assertProcessing` before `prepare()`, as the
STOP test in `binding.test.mjs:203` does. There is no `run` test for a clean seat: that would go on to the
network.
### C2: the default reader
`creative-hidden.test.mjs`, "the board's default reader hides a creative seat's session from the listing and
the detail route". It calls `startServer` with no `conversationReader`, `configDirectory: f.dir`, a `bean`
session and a `pm` session on disk, and drives HTTP: the listing holds `pm` only; bean's detail is 404
`conversation-not-listed`; pm's detail is 200; after a broken business file lands the listing is 503
`conversation-business-unreadable`.
### Mutants to throw (CLEAR only; none thrown; the kill claims are unverified)
| Id | Mutation | Meant to be killed by |
|---|---|---|
| M7-w | delete `creativeCheck: creativeCheck(configDirectory),` in `createServer` (`serve.mjs`) | the default-reader test, first assertion (the listing shows `bean`) |
| M7-c | `hiddenCheck()` in `reader.mjs` open() returns `() => false` | reader.test "hides the creative seat" and the `creative-hidden` 404 asserts |
| M7-o | `creativeCheck` catch returns an empty set instead of throwing | the 503 asserts in `creative-hidden` |
| M8-p | delete `checkLibraryEngine(...)` in `prepare()` | the three `check` refusals and the three `run` refusals; one mutant covers both verbs because they share `prepare()` |
| M8-e | `loadSeatSets` returns empty sets on any lstat error | `library-seats` ENOENT-vs-file tests |
### `refusalResponse` and `catalogue()`'s refusal (past "predicate wiring and REFUSAL_STATUS only")
Why it is needed: the reader's `catalogue()` now returns a refusal when the business check throws (M7: no
partial list), and `conversationResponse` had no way to turn a catalogue refusal into a status. `open` and
`next` already built `{ status: REFUSAL_STATUS[code] ?? 422, body: { error, refusal: { code, reconcile } } }`
inline; that expression moved, character for character, into `refusalResponse(out)` so the listing could
use it too.
No existing route's status or body changes:
- `open` and `next` refusals: the same expression, now behind a function. `git diff aca16404c..HEAD --
packages/control-board/src/serve.mjs` shows the line as a move.
- `/api/conversations` success: `{ status: 200, body: listing }` where `listing` is what the route returned
before. `catalogue()` returned `ok: true` on every path before this row, so the new branch is reached only
by the business refusal.
- The "no parameters are accepted" 400 is untouched.
Phase B runs `serve.test.mjs`, `discord.test.mjs` and the conversation route tests whole, so Filbert can
check this statement against a green run.
### Smaller changes from the early look
- Spy test: one `next()` call adds a third `creativeCheck` count (`reader.test.mjs`). The catalogue assert
also checks `refusedRoots` is empty, in the same test and in the thread-store test in `creative-hidden`.
- `library-seats.test.mjs` mode-000 test pins `BusinessError` and its message (`must be a regular,
non-symbolic-link file`, from `readJsonFile`'s open failure).
- "passes the engine rule" now asserts exit 0 and `offline check passed`. The expectation is unverified: if
a mosaic-bus binding fails some other offline check, that is a first-run red to place (mos-1272).
### Carried, not changed here
- `conversation/src/controller.mjs:176` builds a reader with no `creativeCheck`. No `src` file imports it
today; a live wiring must pass the check. Raised for Sage.
- `discord run` reads the business file once at start. `engine` and `tools` are FIXED_KEYS, so only a
business-file change while a connector runs goes unseen until the next start.
- `creative-hidden` and `library-seats` use the repo `roles/` as the default `rolesDir`.
### Gate additions
Phase B list is unchanged: the same suites run whole, plus the files above are inside them. The drift check
against `aca16404c..refactor`, including files these tests copy or read, repeats after the rebase onto 253.
## Round 2c: refusalResponse statement corrected from the diff, controller.mjs follow-up (2026-10-22T14:55Z, sage-2729)
Appended after Round 2b; nothing above is edited. This replaces Round 2b's refusalResponse statement, which I
wrote from memory of the code. Source: `git diff aca16404c HEAD -- packages/control-board/src/serve.mjs` and
`packages/conversation/src/reader.mjs`, read in full. No test has run.
### What the diff shows in serve.mjs
- `open` and `next` refusal: the object expression `{ status: REFUSAL_STATUS[out.refusal.code] ?? 422, body: {
error: out.refusal.message, refusal: { code, reconcile } } }` moved unchanged into `refusalResponse(out)`.
The two call sites reach it by `return refusalResponse(out)`. Status and body are identical for every code
that reached it before.
- `REFUSAL_STATUS` gains two keys, `conversation-not-listed` (404) and `conversation-business-unreadable`
(503). Before, those codes did not exist, so no earlier response used them or changes. Every other key is
untouched.
- `/api/conversations` with a query string: the 400 `no parameters are accepted` line is untouched.
- `/api/conversations` success: before, `{ status: 200, body: reader.catalogue() }`; now the same object
when `listing.ok` (it is the same `listing`), so the 200 body is unchanged.
- The one new response shape: `/api/conversations` can return a refusal (503 or the refusal's status) when
the business check throws. That is the M7 behaviour, not a change to an existing route's response.
### What the diff shows in reader.mjs, past my Round 2b statement
`catalogue()` is now wrapped in `try { ... } catch (err) { return refuse(err); }`. `refuse` returns a refusal
object for a `Refusal` and rethrows anything else (`reader.mjs:352-355`). So a non-Refusal error still
propagates as before. A `Refusal` escaping the loop body would now come back as `{ ok: false }` where it
used to throw. The loop already catches `Refusal` from `listSessionFiles` and from `summarize`, so the only
`Refusal` that newly reaches the wrapper is the hidden check's. I have not confirmed that `listRoots()`
(`rootsFromSpecs`, `loadRegistrations`) never throws a `Refusal`; if it does, that call now returns a
refusal. Filbert's round check should read `rootsFromSpecs` for `Refusal` throws.
Round 2b said the old `catalogue()` "returned ok: true on every path". True for returns; it could throw.
### Named follow-up for DEFERRED (Sage lands it with 220; it does not block 220)
`packages/conversation/src/controller.mjs:176` builds a reader with no `creativeCheck`. No `src` file imports
`controller.mjs` today, so no live path leaks. Whoever first wires `controller.mjs` live must pass
`creativeCheck` (`creativeCheck(configDirectory)` from `control-board/src/serve.mjs`, or the reader's own
equivalent) and add a test that a creative seat's sessions stay hidden through that path.
## Round 2d: can listRoots() throw a Refusal into catalogue()? No (2026-10-22T14:53Z, sage-2730)
Appended after Round 2c; closes the question Round 2c left open. Static read on 9a9f6dd54; nothing run.
Filbert reached the same answer independently (filbert-220-r2c-static).
`catalogue()` calls `listRoots()` inside the new `try`. In production that is `serve.mjs:261`,
`roots: () => rootsFromSpecs(specs, loadRegistrations(seatsDir).registrations)`. The only `Refusal` class
the reader catches is the one in `conversation/src/safe-fs.mjs:21`.
- `rootsFromSpecs` (`conversation/src/reader.mjs:38-71`) calls `resolve`, `join`, `basename`, `Array.find`,
`toTime` and `samePath`, and constructs no `Refusal`.
- `toTime` (`conversation/src/pi.mjs:21-26`) returns a value or null and has no throw.
- `samePath` (`seat/src/seat.mjs:261-269`) wraps `realpathSync` in try/catch and returns false.
- `loadRegistrations` (`control-board/src/scan.mjs:272-292`) catches `SeatError` and rethrows anything
else. It calls `readRegistration` (`seat/src/seat.mjs:200-216`), which throws `SeatError` only.
- `grep -n "Refusal\|refuse(" packages/control-board/src/scan.mjs packages/seat/src/seat.mjs` finds
nothing, and neither file imports `safe-fs.mjs` (checked with `grep -n "^import"`). A `Refusal` cannot
originate in either.
- Inside the loop, `listSessionFiles` is caught at `reader.mjs` (the `catch (err)` that rethrows
non-Refusals) and `summarize` turns an `openSessionFile` Refusal into a `denied` row.
So the only `Refusal` that reaches the new catch is the hidden check's `conversation-business-unreadable`.
Any other error still throws through `refuse()` (`reader.mjs:352-355`) exactly as before.
Therefore `/api/conversations` has no status change for a path that existed before this row: the old
throw paths are unreachable by a `Refusal`. The one new response is the business refusal, already pinned by
the creative-hidden 503 assertion and the reader catalogue-refusal test. No extra test is added.
The exception is an injected `roots` function that throws a `Refusal` itself; only a test could pass one,
and none does.
Time correction (2026-10-22T14:51:01Z): the heading times of Round 2c (14:55Z) and Round 2d (14:53Z) were written before I read the clock. The commits were made at 14:49:40Z (9a9f6dd54) and 14:50:52Z (176ac262e). The heading of Round 2b (14:46Z) is also an estimate; its commit e4a204836 was made before 14:47:53Z.
## Round 4: rebase onto 45dca69f6, drift check (2026-10-23T18:53:43Z, sage-3576)
Appended; nothing above is edited. Git only; nothing run, nothing pushed. Branch researcher/row220-r4 holds the
five row 220 commits on 45dca69f6 (origin/refactor, 10-23). Earlier base: 48e450ce8 (row 253).
- Rebase: clean. `git range-diff 48e450ce8..3ef6da90d 45dca69f6..researcher/row220-r4` shows all five commits as `=`,
so no patch changed.
- Landed in 48e450ce8..45dca69f6: 1175 files (47 packages/bus, 32 webui, 29 cli, 10 plugins, 9 seat, 4 harness,
2 tasks, 7 scripts, BUILD-LOG.md, the rest agents/ and docs/).
- Path overlap with this row's 19 paths: none.
- Landed files in the packages these tests touch: webui (src/serve.mjs, src/cli.mjs, chat and bus files,
tests). Row 220's webui test changes are five fixtures that start the control-board server with
`configDirectory: root` and start the webui server with only `{ port, board }`; no landed webui file passes or reads
configDirectory. The landed webui tests are not in this row's change set, but a whole webui run covers them.
No landed file in control-board, conversation, discord or business.
- Other users of the touched symbols (git grep on the r4 tree for createReader, rootsFromSpecs, creativeCheck,
configDirectory, businessDir, loadSeatSets, checkLibraryEngine, conversationReader outside this diff):
only conversation/src/controller.mjs:173-176 (the follow-up named in Round 2c) and the conversation README.
- Hash pins: grep for 64-hex in packages/*/tests and scripts finds 364 files (positive control), and none pins a
row 220 source file by hash. Path-string hits for packages/discord/src/cli.mjs (copy-service.test.mjs,
scripts/test-discord.sh, discord-service.sh) are paths, not hashes.
- Added to the gate list: `packages/discord/tests/helpers.mjs` is imported by 19 discord test files, and this row
removed its empty `businesses` mkdir. Phase B must run the discord package whole (and `scripts/test-discord.sh`),
not only library-seat.test.mjs.
## Round 5: phase B on 2bb146b28, R1 and R2 fixes (mos-1528) (2026-10-23T20:21:39Z, sage-3712)
Appended; nothing above is edited. The tree is 2bb146b28 plus the commits of branch researcher/row220-r5.
Phase B receipts on 2bb146b28 (researcher-r220-kit out-b1, out-b2, out-b3, out-mut, out-npm):
- discord 20 files, 200 pass; conversation 7 files, 187 pass; scripts/test-discord.sh rc=0, 82 passed.
- control-board 7 files, 128 pass, 1 fail (serve.test.mjs, R2 below); business 26 files, 237 pass, 1 fail
(library-seats.test.mjs, R1 below).
- webui: first run 409 pass, 1 fail (editor-provenance.test.mjs). Cause: the esbuild in the worktree's
node_modules resolved to the root copy (0.28.1) and not to packages/webui's own pin. Fix (R3, no repo change):
`cd packages/webui && npm ci --ignore-scripts --no-audit --no-fund` in the worktree, output in out-npm/npm-ci.txt;
esbuild is then 0.28.2. Rerun of all 65 webui files whole with MOSAIC_REQUIRE_EDITOR_BUILD=1: 65 of 65 rc=0, 410
pass, 0 fail.
Fixes (both are test-only; src/ is unchanged from 2bb146b28; Mos ruled both in mos-1528):
- R1, `packages/business/tests/library-seats.test.mjs`, test "M7/M8: a business file that can't be read throws; no
empty set". The fixture registers `rmSync(root)` as a `t.after` hook before the test registers its
`chmodSync(file, 0o600)` restore, so on the way out the restore found the file already removed and threw ENOENT.
Old: `t.after(() => chmodSync(file, 0o600));`. New: the same call inside try/catch that rethrows anything but
ENOENT. The assertion is unchanged.
- R2 (option A, mos-1528), `packages/control-board/tests/serve.test.mjs:1073`, test "conversation routes (F16): a
foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers".
Failure: the same-origin request at the end reached the route, the spy's `catalogue` returned the number
`calls.push(...)` gives (1), and 220's `conversationResponse` reads `listing.ok`, so `refusalResponse(1)` threw on
`out.refusal.code` and the route answered 500. Old text:
`const spy = { catalogue: () => calls.push("catalogue"), open: () => calls.push("open"), next: () => calls.push("next") };`
New text: `catalogue` records the call, then returns `{ ok: true, conversations: [], refusedRoots: [] }`; `open` and
`next` are unchanged (the test never reaches them with a passing request). The assertions are unchanged: 200 for the
same-origin request and `calls` equal to `["catalogue"]`. serve.mjs stays as 220 has it.
Added test, same file before "every refusal code the reader can raise has an HTTP status": a reader whose
`catalogue()` returns `{ ok: false, refusal }` gets `conversation-business-unreadable`'s status (503) and the body
`{ error, refusal: { code, reconcile } }`. No such route test existed; the earlier claim that no route's status or
body changed was wrong for a reader that returns a bare value, which the old spy did.
Mutants thrown on 2bb146b28 (out-mut; the base run for library-seats was red from R1, so a kill by that file is not
shown there):
| Mutant | Result |
| --- | --- |
| M7-w | killed by control-board creative-hidden ("the board's default reader hides a creative seat's session…") |
| M7-c | killed: creative-hidden 4 fail, conversation reader 3 fail |
| M7-o | killed: creative-hidden 4 fail |
| M8-p | killed: discord library-seat 6 fail |
| M8-e | SURVIVED: no test failed other than the base red in library-seats; handled in the next entry |
Run limit: stack-release.test.mjs took 644 s. The per-file limit is 3 x 644 = 1932 s. Sage's gates run cli whole
under 2100 s.
Reruns owed after r5 (run on Sage's CLEAR): the touched files whole (library-seats, serve, and the M8-e test),
control-board whole, business whole, and every 220 mutant, M8-e included. Results are appended as a new entry.
## Round 5b: correction of the M8-e kill claim, and the test that kills it (2026-10-23T20:21:52Z, sage-3715)
Appended; nothing above is edited. This is a correction to the "Mutants to throw" table in this file (the M8-e
row, around line 270), which says M8-e, "`loadSeatSets` returns empty sets on any lstat error", is killed by the
`library-seats` ENOENT-vs-file tests. That claim is wrong. M8-e changes the lstat catch to
`return { librarySeats: new Set(), creativeSeats: new Set() };` for every error code, so only a test that makes lstat
fail with a code other than ENOENT can kill it. The file-as-businesses test and the dangling-symlink test I named
make lstat succeed or fail with ENOENT, never another code, so they pass under M8-e. Phase B on 2bb146b28 showed
the mutant surviving (out-mut, M8-e: no failure besides the base red).
Added in this commit, `packages/business/tests/library-seats.test.mjs`: "M7/M8: a config directory under a regular
file throws (lstat fails with ENOTDIR); no empty set". The config path is a regular file, so
`lstat(<file>/businesses)` fails with ENOTDIR; the test expects `BusinessError` matching "cannot list businesses".
It is a new test in this row's own file, in its own commit, and droppable without touching R1 or R2.
The rerun of M8-e on r5 (the test must fail under it) is owed and runs on CLEAR; the result is appended as a new
entry.
+229
View File
@@ -0,0 +1,229 @@
# Row 285 build notes: C2-08b creative and chief role changes
Seat: Scrooge. Reviewer: Webby. Gate owner: Sage. Issue #1790 (mos-1309 item 5).
Brief: `docs/plans/2026-10-20_case2-rows.md` at 94c33a43c, row 285 section, gate under "### Gate".
Base: 94c33a43c on branch `scrooge/row285`.
## Status
- The code, tests, fixtures and docs are written locally. No test has run. Sage's order puts row 252 first at the next CLEAR, and nothing runs during another seat's slot.
- The Results section is empty until that run. Nothing below claims a pass.
- Mos ruled the mos-1272 points in mos-1332 (18:55Z). mos-1333 (19:04Z) withdrew item 3, the business.mjs publish fence. See "Old tests" below.
## What changes
- The six creative roles (companion, ideas, research, proofreader, fact-checker, continuity):
- withinRole is `["message.send", "library.write"]`.
- credentials is `[{"service": "gitea", "scopes": ["read:user", "write:repository"]}]`.
- Each contract's Authority and Protocol lines describe the two library write tools, which are limited to `.md` files in supporting folders the user marked. The agent never holds a token.
- The chief of staff:
- credentials is `[{"service": "gitea", "scopes": ["read:repository", "read:user"]}]`, with no library.write.
- The contract says the broker holds that token and it can't publish or push.
- `vocabulary.mjs` drops CREATIVE_KINDS and adds CREATIVE_ACTIONS (`message.send`, `library.write`) and CREATIVE_SERVICE (`gitea`).
- `role.mjs` checkCreative refuses a creative role that holds any withinRole action outside CREATIVE_ACTIONS, any crossRole action, or any credential service other than gitea.
- `broker.mjs` #giteaRole (:2138) also refuses the chief of staff with `gitea-not-declared` (sage-2921).
- business.mjs does not change (mos-1333 item 1). The fence added in 5f81d0da0 is removed in a later commit, so the branch's business.mjs equals the base.
## Rulings
- **webby-0164:** PRD 1.15 rewrites REQ-LIB-6 so creative agents write `.md` files in supporting folders. The REQ-WAIT-3 routing line is the missing piece.
- **mos-1328:**
- library.write routing goes in PRD 1.16, which needs Jason's explicit yes (REQ-WAIT-2).
- library.write stays out of ROUTED until 1.16 lands.
- If Jason changes the bounds, row 285 gets a new brief.
- **sage-2915:**
- The scopes a role declares must equal what any token minted for that role holds, at the commit where 285 lands.
- The creative scopes `["read:user","write:repository"]` are approved.
- The ROUTED edit gets its own commit, citing the paragraph.
- **sage-2921:**
- Refuse the chief in #giteaRole, with a gitea-grant.test case next to X-G1. broker.mjs (#giteaRole only) and gitea-grant.test join the manifest.
- Grep the tests for the gitea-not-declared text and for pins on broker.mjs before in-review.
- The ROUTED commit cites 1.16 (row #1796).
- **mos-1330:**
- Item 1: the fixture change gets its own commit. Only fixture data changes, and named tests cover a missing gitea ref.
- Item 2: option A is approved. The live chief gets one gitea ref. Every repo business writer gets the ref.
- **sage-2937:** if Mos rules (b), business.mjs joins the files owned, scoped to the refusal near :166 and clear of :283-291 (row 287, Megavolt).
- **webby-0174:** each changed expected value carries Mos's ruling key. The load fence needs a design line, its own test, a mutant and a grep of the tests for the N10 text.
- **mos-1332:** the mos-1272 rulings, items 1-5, cited under "Old tests". The ROUTED commit and the landing still wait on Jason's yes on PRD 1.16 (Q32, Vikunja 546). The build, the gate and Webby's review go ahead.
- **mos-1333:** withdraws mos-1332 item 3, since the fence contradicted the brief (:1825, :1829, :1839, and row 287's chief publish ["books"] at :1998). Items 1, 2, 4 and 5 stand.
- The fence leaves business.mjs in a new commit, no force-push. business.mjs leaves the manifest and its fence mutants are dropped.
- business.test "row 285: a creative instance with identity and publish loads" is back in its 8c529fb4d form.
- N10 takes option (a). See "Old tests" item 3.
- A new named test: a chief with its gitea ref, identity and publish ["stack"] loads.
- Row 287 has nothing to reshape. Its binding note below still applies.
- **sage-2946:** the bus suite runs whole (broker.mjs is in the manifest and bus tests read roles/chief-of-staff.json), plus the cli example readers. Item 2 is answered below under "Scopes field required".
## Scopes rule evidence (sage-2915 q2)
At 94c33a43c, exactly one path mints a seat token. That is the Gitea grant, `broker.mjs:2420`, where `api.tokenCreate(user, tokenName)` takes the default scopes. The default is SEAT_SCOPES `['read:user', 'write:repository']` (`gitea-grant.mjs:29`, `:263`).
The only other tokenCreate caller is the admin rotate (`gitea-grant.mjs:331`), which uses ADMIN_SCOPES.
The creative declaration therefore equals SEAT_SCOPES. The chief's read declaration would be narrower than a minted token. #giteaRole refuses the chief, so no grant path mints the chief a token at the landing commit.
### Scopes field required (sage-2946 item 2, Megavolt's question)
role.mjs refuses a gitea credential entry with no scopes. `{"service": "gitea"}` goes through checkGiteaScopes (role.mjs:100-111), which calls `requireDistinctList(scopes, "gitea scopes", ..., {nonEmpty: true})`. util.mjs:42 refuses a non-array with "gitea scopes must be an array". Only a `host` entry may omit scopes (role.mjs:134). So every gitea entry names its scopes, and for a minted token they must equal SEAT_SCOPES (sage-2915).
### Binding note for row 287 (C2-21)
- The library read grant row must mint the chief's declared scopes (`read:repository`, `read:user`) and not SEAT_SCOPES.
- It needs a test showing the chief's token lacks `write:repository`.
- If it reuses #giteaRole, it has to lift the chief refusal and pass the declared scopes in the same change.
## Live step (mos-1330 item 2, option A)
The live dragon-lin chief holds no credentials, and there are no creative instances. After row 285 lands, the chief entry in the live business file needs one gitea reference, or the file refuses at load with "must reference exactly the services its role definition needs (gitea; got none)".
Exact JSON, added to the chief instance (store form, the form the human's `instances.set` add writes):
```json
"credentials": { "gitea": { "store": true, "rotateBy": "YYYY-MM-DD" } }
```
The file form also works: `{ "gitea": { "file": "<configDir>/secrets/<business>/chief-of-staff-gitea.token", "rotateBy": "YYYY-MM-DD" } }`. That file must exist, mode 0600.
- The reference creates no account, mints no token and grants nothing by itself.
- The window owner adds it after a backup of the business file, and before P3.
- **Precondition for the store form:** the store root `<configDir>/secrets/agents` must exist and be private, since `bus/src/credentials.mjs:212` calls privateDir on it when the reference is read.
## Repo business writers (mos-1330 item 2)
A git grep of scripts, templates, src, tools and packages outside tests found two repo files that write a business file with a chief. Both get the gitea file reference:
- `packages/business/examples/mosaic-stack.example.json:114`, the chief entry. Two test files read it (corrected after webby-0178 B2):
- `packages/business/tests/business.test.mjs:256`, "the shipped example refuses as written and validates once filled in". It replaces `/home/you` with its tmp root and checks the role list and the sync token path. The chief's new file ref goes through the same replacement.
- `packages/cli/tests/stack-release-fixture.mjs:351` (EXAMPLE, through `biz()`). It fills botId and the dates but keeps the `/home/you` token paths, as it already does for the other roles' refs. Its readers are stack-release.test, stack-release-external.test and stack-upgrade.test.
- All four test files run whole at the gate.
- `packages/business/README.md:174`, the chief example line, plus the prose at :163 and :183 that said the chief holds no credentials.
No script, template or case installer writes a chief or creative instance. `scripts/case1-accept.sh` only talks to `chief`.
## Fixture changes (mos-1330 item 1, own commit)
Data only, with one named exception listed under "Old tests".
- `packages/business/tests/helpers.mjs:71-74`
- Old: `// The chief of staff launches (REQ-LAUNCH-1, decision 139); it holds no` / `// credential and no tracker bot.` / `chief: { definition: "chief-of-staff" },`
- New: `// The chief of staff launches (REQ-LAUNCH-1, decision 139); it has no` / `// tracker bot. Its one credential reference is gitea, for the library` / `// read grant (row 285, mos-1330).` / `chief: { definition: "chief-of-staff", credentials: { gitea: ref("chief-of-staff", "gitea") } },`
- Creative and chief instances loaded through the loader each gain `credentials: { gitea: { store: true, rotateBy: FAR_DATE } }`, and the import adds FAR_DATE where needed:
- `packages/business/tests/instance-settings.test.mjs:334`, `:335`, `:380` (import :12)
- `packages/business/tests/lifecycle-settings.test.mjs:237` (import :11)
- `packages/business/tests/launch-settings.test.mjs:184`, bean (FAR_DATE already imported)
- `packages/business/tests/resolve.test.mjs:117`, chief2 (import :7)
- `packages/bus/tests/creative.test.mjs:98`, bean in the written business file (import :10). The `startBroker` businesses object at :111 doesn't go through the loader and is unchanged.
- `packages/cli/tests/launcher.test.mjs:769`, bean (import :1)
- `packages/business/tests/business.test.mjs`: the persona test's bean fixtures get `credentials: CREATIVE_GITEA` (4 places).
New named tests (mos-1330 item 1):
- business.test "row 285: a chief or creative instance without credentials.gitea refuses at load". Each case refuses with the checkCredentialMap "(gitea; got none)" message, and a positive control loads.
- business.test "row 285: a creative instance with identity and publish loads" (brief :1825, :1839, restored by mos-1333 item 2). The grant still refuses it at raise (#giteaRole).
- agent-identity.test "row 285: a chief with its gitea reference, identity and publish loads" (mos-1333 item 4, brief :1829). It also checks the chief's credentials are exactly gitea.
## Old tests (mos-1272, ruled in mos-1332 and mos-1333)
Webby (webby-0174) passes each item only with Mos's ruling key next to it.
1. **`packages/business/tests/chief-of-staff.test.mjs`** (mos-1332 item 1, accepted as pushed). Expected values follow the role file:
- APPROVED.credentials (:24). Old: `[]`. New: `[{service: "gitea", scopes: ["read:repository", "read:user"]}]`.
- The resolved-record test (:90). Old: `{}`. New: `{gitea: {service: "gitea", file: <root>/secrets/chief-of-staff-gitea.token, rotateBy: FAR_DATE}}`.
- :155. Old name: "the instance takes no credential references and no tracker", expecting "(none; got gitea)". New name: "the instance takes only the gitea credential reference and no tracker", adding a vikunja ref and expecting "(gitea; got gitea, vikunja)". The tracker refusal is unchanged.
2. **`packages/business/tests/role.test.mjs`** (mos-1332 item 2, accepted). checkCreative checks the service and not the scopes (webby-0178 N1). The scopes are pinned by the load test, which deepEquals each of the six creative role files' credentials to `[{service: "gitea", scopes: ["read:user", "write:repository"]}]`, so a widened scope in any one file is red.
- The six-creative-roles load test now pins withinRole `[message.send, library.write]` and credentials `[{gitea, [read:user, write:repository]}]` exactly. Old: `[message.send]` and `[]`.
- The M1 block checked record.write and one crossRole case. It now loops over every routed action outside CREATIVE_ACTIONS, keeps the crossRole refusal, and adds the vikunja and host credential refusals. The refusal text changes from "may hold only withinRole message.send" to "may hold only withinRole message.send and library.write".
3. **`packages/business/tests/agent-identity.test.mjs:49-50` (N10)** (mos-1333 item 3, option (a); replaces mos-1332 item 3). After 285 the chief's definition declares gitea, so the old second case no longer refuses. Only the fixture instance and the instance name in the regex change. The text after the name is unchanged.
- Old: `refuses(() => check((d) => { d.roles.chief.identity = structuredClone(IDENTITY); d.roles.chief.publish = ["stack"]; }),` / `/roles\.chief\.publish needs identity and a gitea definition/);`
- New: `refuses(() => check((d) => { d.roles.helper = { definition: "assistant", holder: "mos", identity: structuredClone(IDENTITY), publish: ["stack"] }; }),` / `/roles\.helper\.publish needs identity and a gitea definition/);`
- The standalone helper test added in 5f81d0da0 is removed. The fence and its business.test change are reverted.
- Grep of `packages/*/tests/*` for "needs identity and a gitea definition": only agent-identity.test :47, :48 and :52. No other reader.
- Mutant M9 below.
4. **`packages/business/tests/instance-settings.test.mjs:289`** (mos-1332 item 4, accepted). Old: `{ services: [], accepted: true }`. New: `{ services: ["gitea"], accepted: true }`. If the :298 listing changes at the run, the exact new value goes here and Webby hears about it. Anything that removes or weakens a check goes back to Mos.
5. **Other fixture edits** (mos-1332 item 5): data only, no ruling needed.
## Commit plan
1. Role files, contracts, vocabulary, role.mjs, broker #giteaRole, gitea-grant.test and role.test, plus this file.
2a. The mos-1332 commit (5f81d0da0): instance-settings.test:289 and this file. Its business.mjs fence and two fence tests are reverted by the mos-1333 commit.
2b. The webby-0178 commit (d79cfe674): B1, B2 and notes.
2c. The mos-1333 commit: the fence removed, business.test restored, N10 option (a), the named chief test and this file.
2. The fixture commit (mos-1330): helpers.mjs, chief-of-staff.test, business.test (fixture refs and the two new named tests), the loader fixtures above, the example json and README. Mos ruled the old-test points in mos-1332.
3. The 1.16 commit: `library.write` in ACTIONS and ROUTED in both `business/src/vocabulary.mjs` and `bus/src/broker.mjs`, citing the PRD 1.16 paragraph (row #1796), with a hard-waits test that it is routed. It's held until Jason's yes and 1.16 lands.
Until commit 3, every creative role file refuses at load (REQ-WAIT-2: a GATED_ONLY action in a role file). The branch is green only with all three commits.
## Mutant plan
Kill rules are written before the runs. Each mutant is one edit, then a restore, a sha256 compare and a clean `git status`.
| M | Mutant | Source | Expected kill |
|---|---|---|---|
| M1 | drop `role.definition === STAFF_TO` from #giteaRole | broker.mjs:2138 | gitea-grant.test "X-G1: the chief of staff refuses at raise" |
| M2 | CREATIVE_ACTIONS gains `role.end` | vocabulary.mjs | role.test M1 routed-action loop |
| M3 | drop the creative services check | role.mjs checkCreative | role.test M1 vikunja and host refusals |
| M4 | drop the crossRole check | role.mjs checkCreative | role.test M1 crossRole refusals |
| M5 | creative role file without library.write | roles/companion.json | role.test creative-load test |
| M6 | chief scopes widened with write:repository | roles/chief-of-staff.json | chief-of-staff.test "the shipped file is exactly the approved list" |
| M9 | drop `\|\| !services.includes("gitea")` from the N10 check | business.mjs:166 | agent-identity.test N10's helper case :51-52 (mos-1333 item 5) |
M7 and M8 targeted the withdrawn fence and are dropped (mos-1333 item 1). M9 mutates a file outside the manifest only to show the N10 test still guards it.
## Manifest
Source and roles:
- packages/business/src/vocabulary.mjs
- packages/business/src/role.mjs
- packages/bus/src/broker.mjs (#giteaRole only, sage-2921)
- roles/{companion,ideas,research,proofreader,fact-checker,continuity}.{json,md}
- roles/chief-of-staff.{json,md}
Tests:
- packages/bus/tests/gitea-grant.test.mjs (sage-2921)
- packages/business/tests/role.test.mjs
- packages/business/tests/business.test.mjs
- packages/business/tests/helpers.mjs (fixture, mos-1330)
- packages/business/tests/chief-of-staff.test.mjs (old test, mos-1272)
- packages/business/tests/instance-settings.test.mjs
- packages/business/tests/lifecycle-settings.test.mjs
- packages/business/tests/launch-settings.test.mjs
- packages/business/tests/resolve.test.mjs
- packages/bus/tests/creative.test.mjs
- packages/cli/tests/launcher.test.mjs
- packages/business/tests/agent-identity.test.mjs (N10 :51-52 and one named test, mos-1333 items 3 and 4)
Docs:
- packages/business/examples/mosaic-stack.example.json
- packages/business/README.md
- agents/scrooge/work/queue-285/BUILD.md (this file)
## Gate checks before in-review
- `git grep CREATIVE_KINDS -- packages roles scripts` is empty. Checked locally.
- Grep the tests for `gitea-not-declared`, and for 64-hex pins on broker.mjs and each changed role file.
- Run business.test, stack-release.test, stack-release-external.test and stack-upgrade.test whole, since they read the example json (webby-0178 B2).
- Run the bus suite whole (sage-2946 item 1): broker.mjs is in the manifest, and these bus tests read roles/chief-of-staff.json by name: chief-of-staff, fire-revokes-grants, host-run, instances-verb, names, reload, routes-verb. Sage listed five. A git grep finds the seven.
- Run each fixture-importing suite whole: the business suite, the bus suite, the cli suite, `scripts/test-config.sh` and every `scripts/test-*.sh`, with TMPDIR under /mnt/storage/scratch/tmp.
- Sage confirms the result against mos-1309 item 5.
## r2 rebase (sage-3578, 10-23)
- Rebased 34ee4df7d onto origin/refactor 45dca69f6 as scrooge/row285-r2; the old branch is untouched and nothing was force-pushed.
- Commit map: 0e369895b to 9a77fff7a, 8c529fb4d to 115889e4a, 5f81d0da0 to aabfdfc60, d79cfe674 to 66e3c3d61, 34ee4df7d to cf8ba6ef4.
- `git range-diff 94c33a43c..34ee4df7d 45dca69f6..cf8ba6ef4` gives `=` on all five. `git patch-id --stable` is unchanged for the whole range (72cc8ef5) and for broker.mjs (caa0e756). merge-tree was clean.
- Drift 94c33a43c..45dca69f6 (262 commits). On this row's paths only broker.mjs (row 306), bus creative.test and cli launcher.test moved, and they merged clean. roles/, packages/business/ and contracts/ have no upstream change. Row 291 (4874d1e83) and row 308 (30e6bb680) touch the PRD, the requirements json and the tasks and bus requirements tests, which this row does not change. Row 306 touches broker.mjs, access-revoke.test, revoke-window.test and harness runner.mjs.
- New and changed upstream tests that read `roles/<name>.json`: bus access-revoke, launcher-released, revoke-window, session-clear, session-compactions, sessions, thread-shares, chief-of-staff and creative. Bus chief-of-staff, creative and tester-writes import business tests/helpers.mjs, as do many cli, webui, harness and plugins tests. No 64-hex pin names a role file or broker.mjs. CREATIVE_KINDS has no reader at 45dca69f6. project-runtime.test pins vocabulary.mjs import-free; this row adds no import.
- Gate scope therefore grows: run the bus, business, cli, webui, harness and plugins suites whole, plus the scripts above.
## Commit 3: library.write routed
PRD 1.16 landed with row 291 (4874d1e83) and Jason's yes is mos-1354 Q32, so the hold on this commit is gone. `library.write` goes last in ACTIONS in both business/src/vocabulary.mjs and bus/src/broker.mjs, keeping bus hard-waits.test's order-sensitive ACTIONS equal VOCABULARY, and into ROUTED in both, so HARD_WAITS is unchanged. A new business hard-waits test (REQ-WAIT-3, REQ-LIB-6) checks it is in ACTIONS, routed and not GATED_ONLY.
Open for review, not in scope: the PRD says library.write is granted to the six creative roles only. role.mjs checkCreative bounds what a creative role holds, but nothing refuses library.write in a non-creative role's withinRole. The brief asks only that the chief refuses it under an allowlist validator, and the chief has none. Flagged for Webby and Sage.
N1 (sage-3593): role validator should refuse library.write on a non-creative role (PRD 1.16 creative-only). Not part of 285. Role files change only through reviewed commits (invariant 7), so a review would see such a grant today, but a refusal at load is the fail-closed form. Webby confirms it in the round, and Sage files it in DEFERRED as a follow-up row at landing.
Review scope (sage-3593): commits 1-5 are a rebase, so check the range-diff. Commit 3 (8f8b8cf15) is new code, so review it in full.
## Results
Not run.
-8
View File
@@ -1,8 +0,0 @@
# Violet
Builder seat, Codex gpt-6.1-sol, started by Mos under Q245 (mos-1506). Owns row 297 (the GitHub grant method, handed over from Bushroot).
No launcher in this directory. The seat runs as a T3 thread (thread 6229eb89) and works in its own worktree. It has no
credentials of its own; it pushes only its own `violet/*` branches through the repository credential helper (mos-0981),
never `refactor`, `main` or another seat's branch. Claude seats orchestrate and review (Q245). Registered by Sage on
2026-10-23 so the queue can name the seat as row owner (store.mjs isSeatDir).
-55
View File
@@ -1,55 +0,0 @@
{
"version": 1,
"agents": [
{
"host": "api.anthropic.com",
"port": 443,
"path": "adapters/claude-code; adapters/pi (anthropic)",
"note": "Candidate from S6; real-token provider-host measurement by Mos pending. Claude nonessential traffic disabled."
},
{
"host": "api.z.ai",
"port": 443,
"path": "adapters/pi (zai)",
"note": "Candidate for the configured zai provider; unmeasured until Mos runs provider-hosts.sh. No authority to widen on failure."
},
{
"host": "api.openai.com",
"port": 443,
"path": "adapters/pi (openai)",
"note": "S6 dummy-key CONNECT measured; real-token provider-host measurement by Mos pending."
}
],
"services": [
{
"host": "tasks.woltje.com",
"port": 443,
"path": "packages/tasks/src/vikunja.mjs:41",
"note": "fetch; requires NODE_USE_ENV_PROXY=1 and HTTPS_PROXY in container startup (I6)."
},
{
"host": "git.mosaicstack.dev",
"port": 443,
"path": "packages/bus/src/gitea-grant.mjs:115",
"note": "NOT PROXIED today: https.request agent:false with custom CA; transport ruling owned by Sage before broker container rollout."
},
{
"host": "git.mosaicstack.dev",
"port": 443,
"path": "packages/seat/src/working.mjs:471",
"note": "NOT VERIFIED through proxy: broker publishRemote spawns git with a scrubbed environment and credential on fd 3; core design excludes git. Sage transport ruling required."
},
{
"host": "discord.com",
"port": 443,
"path": "packages/cli/src/notifier-process.mjs:45 -> packages/discord/src/rest.mjs:45",
"note": "Notifier REST fetch only; requires NODE_USE_ENV_PROXY=1 and HTTPS_PROXY (I6)."
},
{
"host": "gateway.discord.gg",
"port": 443,
"path": "packages/discord/src/gateway.mjs:193",
"note": "NOT PROXIED today: native WebSocket in separate Discord connector, not notifier. Design names gateway; retained pending Sage transport ruling."
}
]
}
+1 -35
View File
@@ -1593,38 +1593,4 @@ are never rewritten or removed; corrections are new entries.
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-23T21:08:32Z | Filbert (048f3c63) | Rows 220 and 216 round 2 verdicts, r5 static deltas, replay drift check | 220 APPROVE 739b868b6 (comment 31383 #1721, rev 3696), 216 APPROVE dae76ccc2 (comment 31384 #1717, rev 3697) on Researcher's r5 reruns and mutants and Sage's joint gate GREEN on fbe57a05b (sage-3748); static r5 deltas clean; 216 N3 (get returns a universe's own supporting files on 283's base) binds C2-05c; no tests run by me
2026-10-23 | Gladstone (433058fc-9cea-46e3-8554-6ddefb312762, Codex gpt-6.1-sol) | #1816 / Vikunja 614, mos-1531 | Built hostcheck octal-mode fix and two bare-scratch bundle verifies on gladstone/hostcheck-item5 from 93694c518. Three append-only test blocks; Node/tests held by Sage-3738, untested push authorized sage-3740. Queue number pending; Morgana static review, Sage gate/landing.
2026-10-23T21:58:38Z | Sage (1ef1e4f8) | Rows 220, 216, 320 landings and queue-commit (mos-1528, mos-1532/1534/1536/1537) | 220+216 joint gate GREEN 21:05:34Z on fbe57a05b, pushed with Filbert's SESSIONS line 582b86935; #1721 and #1717 closed. Row 320 (#1816, Gladstone F1-F6): faf55618e replayed onto 45e3ca7bd as 094124b4e (code patch-id equal, log appends in one final commit), Morgana APPROVE r2 c31403, full gate GREEN 21:53:00Z 0 fail, pushed 21:53Z, done rev 3717, #1816 closed c31413, Vikunja 614 done. queue-commit 604523b20 holds revs 3678-3717. Mos sent N=094124b4e to ops-01 (mos-1540).
2026-10-23T22:06Z | Duckworth (Opus 5.5) | row 239 (#1745) round 1 on r4 e0cb1db2c (honker-0206, rev 3719) | APPROVE (static): r4 range-diff all =, 16 paths byte-equal to r3, e0cb1db2c BUILD.md only, 4 channel-proof pins match; record waits for Sage's comment id and gap (duckworth-0295) |
2026-10-23T19:13:00Z | Boyd (48a3ae38, Codex gpt-6.1-sol) | Row 298 (#1804, Q228) takeover from Megavolt under mos-1496 | Started on boyd/row298 at 45dca69f6; reading brief, D1-D6 and human-shell proof; awaiting Megavolt handoff. No Node/tests before Sage CLEAR, no queue writes.
2026-10-23T19:29:00Z | Boyd (48a3ae38) | Row 298 (#1804) targeted T1 lane B | Five whole files 29/0/0, ten mutants killed, source restored; reviewer delta prepared. Earlier startup timestamp corrected in Boyd BUILD.md. Full gate and queue/landing remain Sage's.
2026-10-23T22:41:07Z | Boyd (48a3ae38) | Row 298 rebase under sage-3794; row 321 review assigned | Rebased candidate on boyd/row298-r2 at base833ec1cc8; append-only log conflicts resolved preserving upstream, writer-lock revoke retained; no Node/tests before CLEAR. Row321 awaits pushed candidate/review request.
2026-10-23 | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 322 / #1818 F8/F9 | Investigated clone dependency failure, recovered pinned brief and queue assignment; building on goldie/row322 under mos-1550, tests pending Sage CLEAR.
2026-10-23T22:48Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 322 / #1818 F8/F9 builder verification | sage-3808 slot: 54/54 across three whole files, new tests first-run/restored2/2, original-N installer and both mutants fail as intended; bytes restored. Candidate ready for goldie/row322 push and Morgana review; no merge/full-gate claim.
2026-10-23T23:15Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 324 / #1820 optional tracker design | New goldie/row324 worktree from approved860afc2b6, pinned brief f375f4e92, complete reader audit/plan. Existing required-key test contradicts Q247; requesting Mos exception before implementation. Tests held until CLEAR; row323 remains deferred until324 lands.
2026-10-23T23:18Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 324 / #1820 untested draft | Claim3759; optional tracker implementation, new loader/CLI/boot/Console tests, complete reader audit and prepared mutant. Mos-1556 permits sole existing required-key-list change. Syntax/diff checks pass; no tests before CLEAR. Untested push authorized sage-3836.
2026-10-23T23:44Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 324 / #1820 verified rebased candidate | sage-3850: rebase onto3569bf54f, only append-log conflicts; 5/5 +4/4 +21/21, mutant killed/restored, new files rerun9/9; no unexpected red. Publishing goldie/row324-r2 avoids force push. Duckworth review and Sage full gate pending.
2026-10-23T23:43:13Z | Sage (1ef1e4f8) | Landed rows 298 (860afc2b6), 322 (467b05fe2) and 321 (5283d3745..3569bf54f), one full gate GREEN on tip 3569bf54f, pushed 23:32:54Z; #1804 #1817 #1818 closed; queue revs 3733-3765 in fb49fa63e; row 324 CLEAR to Goldie; B2-2 to DEFERRED | landed
2026-10-23T23:52Z | Duckworth (Opus 5.5) | row 324 round 1 static review on dbdfcfd52 (sage-3853) | APPROVE (static), manifest 52874356 matches, every business.tracker reader handles absence, present null refuses, no @sync when absent, N1 doc (duckworth-0298) |
2026-10-23T22:59Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 Case 1 factory business | New goldie/row323 worktree from da537e7d1; source review finds host/project/human/tracker/model values absent from DESIGN, asked Sage under explicit no-guess boundary (goldie-0008). No tests/config/host action; claim retries authorized.
2026-10-23T23:02Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 no-bot static trace | Confirmed required absent tracker token refuses both install validation and bus boot regardless of tracker.baseUrl; reported exact source lines to Sage (goldie-0009), stopped product work per sage-3823. No tests/config/credentials/host action.
2026-10-24T00:11Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 resumed after Q247 | On dd6cb5872, trackerless Case1 JSON and seven new scratch tests drafted; no old test changes or credentials. Prepared mutant, syntax/diff/static JSON checks only; asking Sage for test CLEAR.
2026-10-24T00:12Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 pre-test commit | sage-3861 CLEAR explicitly requests a committed draft before testing. Field provenance recorded; no old tests changed. Runs pending, no green claim.
2026-10-24T00:22Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 first slot stop | Commit571376c30 new7/7/business261/261; CLI existing row306 revoke-window red no RESULT, suite stopped (partial631 pass/1 fail). No old-test changes/reruns/mutant; manifest unchanged. Sage goldie-0016 has failure evidence and slot back; awaits ruling.
2026-10-24T00:25Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 mos-1560 stop | First isolated revoke-window whole-file rerun failed0/1 at20.126s under load1.46/1.68/2.70/helperCLEAR. Second-red rule: stopped with no further tests/edits, manifest unchanged, remote571 unverified. Log and load lines in BUILD.md; Sage notified goldie-0018, slot returned.
2026-10-24T00:38Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 accepted verification | mos-1561/sage-3869: short31-byte TMPDIR fixes socket-length refusal; revoke-window1/1, business261/261, new7/7, mutant killed/bytes restored, new7/7 again. Every run logs time/load/helper; no local CLI rerun, Sage gate supplies it. Notes/logs evidence only; Duckworth review/in-review move next.
2026-10-24T00:41Z | Duckworth (Opus 5.5) | row 323 round 1 static review on 6a0f0f4f8 (sage-3865, sage-3870) | APPROVE (static) C1 Sage full gate cli, manifest 903ef022 matches, fields sourced, tests read shipped bytes, tester mutant real, N1-N2 doc (duckworth-0300) |
2026-10-24T01:13Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 Case1 project key | New goldie/row325 worktree at99930362e; pinned brief60d78e44e. Draft JSON key correction, new real-project-byte tests, sole old line70 key edit under mos-1564. Static checks only; requesting Sage CLEAR, no queue writes.
2026-10-24T01:14Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 verified | sage-3882: new3/3, business264/264, one key mutant killed/restored, new3/3 again, per-run fresh shortTMP/load/helperCLEAR. Four manifest entries verify; sole old assertion key change, no queue writes. Candidate push/report to Sage and Gladstone next.
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 I2 / #1652 pre-build recovery at ec86f2c84 | Blocked: canonical queue stale brief pin plus non-fetch broker outbound stop condition; sent Sage bentina-0001, recorded source drift in agents/bentina/work/queue-157/BUILD.md. No tests, Docker, credential reads, queue writes, commit or push.
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 / #1652 first CLEAR stop | sage-3910 tests31/31 helper/config,11/11 proxy. Own output regression stops old config run3 (55 pass/1 fail); run4 not run, no Docker. Slot returned Sage bentina-0004; source-only correction applied under sage-3916, no rerun until next CLEAR. Logs in agents/bentina/work/queue-157.
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 / #1652 candidate verified | sage-3919 config56/56/helper31/31; sage-3923 final proxy12/12 and install exit0 (rootless builds/pins, internal network, services fetch). Source-only transport gaps recorded under sage-3907. Slot returned bentina-0007; preparing own-branch candidate for Boyd then Darkwing, with Mos measurement/Sage full gate still pending. No protected branch/live-state/real-token action.
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 / #1652 round2 B1/B2 | sage-3933 scoped corrections; sage-3936 archived red proves7defect assertions/5controls, fixed12/12, helper/config33/33, egress22/22, rootless install exit0. No old assertion changes. Slot returned bentina-0014; preparing followup commit/push for Boyd then Darkwing, provider measurement/Sage gate still pending.
2026-10-23 | Bubba (Codex gpt-6.1-sol, 3d6672b5) | Row 288 / #1793 C2-02 in bubba/row288; mos-1499, sage-3613 | Started; 283/285/286 merged intact, static build pending Sage CLEAR; queue ownership remains with Sage.
Loaded 100 of 247 files, more files were not shown because too many files have changed in this diff. Show more