Compare commits

...
Author SHA1 Message Date
jason.woltje aed6796633 honker: row 256 (#1761) b2: Webby's N1 (webby-0124): instance-settings.mjs:30 and agents.js:46 comments name the assistant, not the chief of staff; BUILD.md note; comment-only, unrun 2026-10-22 04:00:40 -05:00
jason.woltje ec82624742 honker: row 256 (#1761) b1: the human deletes a creative instance and any other actor is refused organization-creative-human-only; protectedRefusal is the delete check and the listing's per-reader endProtected; actor threaded through planDelete/checkDelete/instances.check; fire unchanged; DEFERRED entry closed for delete; tests T1-T8, unrun 2026-10-22 03:57:05 -05:00
jason.woltje 8c50481d2f honker: row 256 design draft 2 (webby-0122): T4 uses the last-listed refusal (R1), endProtected keeps its name with comments rewritten (Q1), bus test in creative.test.mjs (Q2), M1 killed by T2 only and the chief's human-required pinned (N1), M4 for the actor threading (N2) 2026-10-22 03:51:41 -05:00
jason.woltje c3e0bd9db6 honker: row 256 (#1761) design draft 1: one protectedRefusal predicate for delete and the listing's per-viewer endProtected, actor threaded through planDelete/checkDelete/instances.check, fire unchanged, persona file left in place 2026-10-22 03:46:48 -05:00
jason.woltje be2b008cc0 queue: revs 3156-3162: row 194 Honker in-review and failed request (3156/3157), Sage review request (3158/3159), Webby verdict (3160), Sage landing note and done (3161/3162) 2026-10-22 03:39:38 -05:00
jason.woltje ab7377bcf1 docs: row 194 landing BUILD-LOG and SESSIONS lines (Sage) 2026-10-22 03:38:16 -05:00
jason.woltje fd6dd327c3 honker: row 194 b15 (b') (sage-2507): T15 drops the exited reviewer stand-in's registry entry by hand, the N6 convention, so the post-resume coder launch isn't refused on an entry with no provider/model; BUILD.md notes for (a) and (b') 2026-10-22 03:06:41 -05:00
jason.woltje 5fd178e44d honker: row 194 b15 (a) (sage-2501): the Broker constructor's harnessOn, testerOn and roleFile type checks move ahead of the store reads, so a null store gets the argument error first (chief-of-staff T8b); 191's lines unchanged 2026-10-22 03:06:41 -05:00
jason.woltje 345f3ace67 honker: row 194 b8 r3 docs: correct N4, only session.ended clears the alive flag today 2026-10-22 03:06:41 -05:00
jason.woltje 54ec93d06f honker: row 194 b8 r3 docs (webby-0100, sage-2290): N1 corrects the N6 clock caveat (the bus clock runs ahead, so the error is over-signal); N2 DEFERRED the 64-stop done cap; N3 and N4 follow-ups; no product change 2026-10-22 03:06:41 -05:00
jason.woltje 4f68485272 honker: row 194 b8 r2 (sage-2289): a fire stop stopRun can't confirm (unknown) reads as lifecycle.end.alive with reason unknown, never killed, and the Console flags it; cli T9a no-runtime-record test, bus T9a reason checks, M56; notes 2026-10-22 03:06:41 -05:00
jason.woltje c48239ec3d honker: row 194 b8 notes: base eb3abba12 (row 191), the commit 1, b4 and b6 conflict resolutions file by file, S6 design notes, the unknown-outcome question, mutants and run list 2026-10-22 03:06:41 -05:00
jason.woltje 5d2c274420 honker: row 194 b8 S6 (sage-2241, sage-2246, sage-2261): fire's kill end goes through row 191's stopRun with the fire's request as owner, killed and alive come back as lifecycle.end.killed and lifecycle.end.alive and the Console flags a run still alive; terminate stays SIGTERM-only (V3); halted, the pass closes launches and regenerates processing-halted and a halt joins a fire's stop; terminate's pass narrows to runs spawned by the end's time (N6); T9a, T9b, T15, N6 2026-10-22 03:06:41 -05:00
jason.woltje 9e5ad7c599 honker: row 194 b7 notes: rebase onto dcd8d4dda (row 254), channel-proof pin, N3 as a behaviour change, routes.set keep and M-next, mutant (a) kill file corrected, N6 ruling for S6 2026-10-22 03:06:41 -05:00
jason.woltje 939356a1c2 honker: row 194 b7 (webby-0095 N1, sage-2248): routes.set refuses a kept instance's route to anyone but the human, as the limits writer does 2026-10-22 03:06:41 -05:00
jason.woltje 3521c23c3e honker: row 194 b6 (webby-0094, sage-2241, sage-2245): every instance-writer verb answers restart (B1) and the CLI and Console show it for veto; fire runs the writer's checks against the file before step A, so an unloaded keep can't strand an instance firing; a fire of a delisted name revokes (N1); a host service makes no token reference (N2); only the human re-points a kept instance's model (N3); DEFERRED a human escape for a stuck fire (N4); S6 waits on row 191's stopRun contract 2026-10-22 03:06:41 -05:00
jason.woltje 2311a50e1c honker: row 194 b4 (sage-2164, sage-2166, sage-2217): a hire and the role's hireable list refuse a creative definition; a library mapping change isn't reloaded (T14); the listing's endProtected hides a Delete the server refuses; DEFERRED creative delete; C1 testerNoSignIn closed; row 213's writer-restart pin follows D3 and T16 uses row 196's tester socket 2026-10-22 03:06:41 -05:00
jason.woltje 29daebf548 honker: row 194 b3 sage-1634: commit() checks the revision before the revoke, so a stale delete or fire refuses revision-conflict with nothing revoked (N1); bus T21; option 2 a follow-up 2026-10-22 03:06:41 -05:00
jason.woltje 2f08fd8f23 honker: row 194 b3 sage-1630: BUILD.md follow-up N1, a revoke that succeeds before a refused write (a stale-revision delete does it every time) 2026-10-22 03:06:41 -05:00
jason.woltje b8c3d00362 honker: row 194 b3 sage-1620: credential-revoke-failed says the business file is unchanged and a retry is safe (CLI and Console), W2 checks the log per case 2026-10-22 03:06:41 -05:00
jason.woltje 14124d96d4 honker: row 194 b3 (sage-1605, sage-1610): fire and delete revoke before the write (T20), a human add takes a tracker bot, a store-only delete names the bot; the tester keeps the credential refusal (row 175 W2); channel-proof and project-runtime pins 2026-10-22 03:06:41 -05:00
jason.woltje e7fb59f225 honker: row 194 on row 199's landing cb5fdb064 (sage-1601): honker/row194-b2 85edab7c7 squashed, five conflicts kept both sides (broker imports, constructor, dispatch, the credential and lifecycle verbs; runtime imports; organization write takes detail and context; cli credentials and veto dispatch; vocabulary comment as row 199 wrote it) 2026-10-22 03:06:41 -05:00
jason.woltje e122aad8e2 queue: revs 3148-3155: row 280 Rocko start, in-review and failed review request (3148-3150), Sage review request (3151/3152), Duckworth verdict (3153), Sage landing note and done (3154/3155) 2026-10-22 03:03:05 -05:00
jason.woltje 4c4b8559cf docs: row 280 landing BUILD-LOG and SESSIONS lines (Sage); Rocko's row 280 BUILD-LOG and SESSIONS lines 2026-10-22 03:00:14 -05:00
rocko b0fd28b6f3 docs: keep a slim row 280 packet with full evidence reference 2026-10-22 02:56:40 -05:00
rocko b765f5cb81 test(bus): prove scratch sshd listener ownership before use 2026-10-22 02:28:09 -05:00
jason.woltje da5d3651d7 queue: revs 3138-3147: row 279 Rocko start and in-review (3138-3140), Sage review request (3141/3142), Dewey verdict (3143), Sage landing note and done (3144/3145); row 280 #1785 bus sshd port race added and briefed (Sage 3146/3147, mos-1270) 2026-10-22 02:14:46 -05:00
jason.woltje 82c8281166 sage: brief for the bus sshd port race row (mos-1270, #1785); 279 landing and 194 b16 third-red BUILD-LOG lines; SESSIONS lines (Sage, Rocko 279, Dewey 279 r1 and carry) 2026-10-22 02:12:09 -05:00
rockoandjason.woltje ceadad993c docs: record row 279 regression and mutant evidence 2026-10-22 01:03:26 -05:00
rockoandjason.woltje f5611c937e fix(processing): observe run status after stop deadline overshoot 2026-10-22 01:03:26 -05:00
jason.woltje 343b6dd766 queue: revs 3134-3137: row 277 brief re-pin (Sage 3134), Morgana 277 claim (3135); row 279 #1784 stopRun deadline added and briefed (Sage 3136/3137, mos-1269) 2026-10-22 00:54:47 -05:00
jason.woltje f40f2e770e sage: brief for the stopRun deadline row (mos-1269), the 191 follow-up row 194 b15's gate found 2026-10-22 00:51:17 -05:00
jason.woltje 23c3d62183 queue: revs 3130-3133 (Sage): row 277 #1782 owner thread claims added and briefed; row 278 #1783 Console thread claims added and briefed 2026-10-22 00:17:54 -05:00
jason.woltje 70e289ba73 sage: #1782 design D1 ruling (sage-2487) and the Console thread-claims follow-up brief 2026-10-22 00:14:35 -05:00
jason.woltje ab50de9fe3 queue: revs 3114-3129: rows 191, 205, 268 landed and done (Sage 3116/3117, 3119/3120, 3123/3124, 3126-3129); Rocko 191 in-review 3114/3115; Dewey 191 verdict 3118; Morgana 205 in-review 3121/3122; Duckworth 205 verdict 3125 2026-10-22 00:12:00 -05:00
jason.woltje b3665f5ab1 sage: docs for landings 191, 205, 268; #1782 owner thread-claims brief (mos-1262); re-pin 86f95bb27 plan; phase 2 window draft 3; seat SESSIONS and BUILD-LOG lines 2026-10-22 00:10:08 -05:00
jason.woltje c27207f6e6 row 268 (C2-01c, #1773, Vikunja 464): playgrounds at three levels; classify checks prefixes first
checkMapping takes kind absent, hybrid or draft. A draft has a name and one
or more prefixes and no books, and comes back with books []. A prefix inside
or holding another entry's book refuses library-playground-other-universe,
which replaces library-playground-outside-book. A prefix equal to a book or
holding its own refuses library-mapping-invalid, as does a draft source.
classify returns a playground file's entry and the book holding its prefix,
or null at universe level and in a draft.
2026-10-22 00:04:42 -05:00
jason.woltje ce6a96ad61 morgana: row 205 r2 (sage-2430): put() events carry their decision so the decision trail reaches them; pin checks the thread claim via threadsState, owner read is a follow-up row 2026-10-21 23:49:34 -05:00
jason.woltje 59f708ca21 morgana: row 205a build (sage-2288): trail and replies hide other users' thread messages from owner, reader and tester; hidden answers as unknown; #readable owner-only; design d2 rulings 2026-10-21 23:49:34 -05:00
jason.woltje f751505ada morgana: row 205 design d1 (sage-2280): FED R3 unfiled, so split 205a message filter now and 205b viewer verbs after R3; mutant 3 to 193b2; hidden trail subject returns [] 2026-10-21 23:49:34 -05:00
rocko 0fa7bf421f fix: place processing controls within the Console navigation box 2026-10-21 22:42:13 -05:00
rocko 93d211c295 fix: allocate Console viewport space to processing controls 2026-10-21 22:42:13 -05:00
rocko 08c896e3c5 docs: record processing gate failures and timer fixture correction 2026-10-21 22:42:12 -05:00
rocko 03ed2bccce test: attribute processing timer to its owning script 2026-10-21 22:42:12 -05:00
rocko ad6020754d fix: stop processing controls on Console sign-out and preserve layout 2026-10-21 22:42:12 -05:00
rocko 5c8f983dde test: initialize processing control in Discord CLI fixtures 2026-10-21 22:42:12 -05:00
rocko f1267e49af fix: account for processing controls in image inputs and launch fixtures 2026-10-21 22:42:12 -05:00
rocko d2be97c5fc test: use private tester socket for processing controls 2026-10-21 22:42:12 -05:00
rocko c13c68c813 test: strengthen processing cancellation and refusal guards
Bound the halt-abort expectation, check Discord refusal before malformed journal reads, and isolate HTTP origin and valid oversized-body guards. These test changes are committed for the row 191 gate; execution awaits the coordinated slot.
2026-10-21 22:42:12 -05:00
rocko 4bdc7e0e86 processing: inspectable per-run stop seam for lifecycle fire
Unify halt and fire cancellation by verified run identity with durable first-owner receipts and shared deadlines. Integrate the instance control with the current host, library and tester-socket paths. Focused nine-file evidence is 62 passing tests; broad suites, mutants and review remain pending.
2026-10-21 22:42:12 -05:00
jason.woltje 86f95bb27f queue: revs 3109-3113 (darkwing: row 273 in-review round 1 and review request; filbert: row 273 APPROVE recorded (c30308); sage: row 273 landing note and done after 0bbf0f6d1 (#1778)) 2026-10-21 22:41:05 -05:00
jason.woltje 5365adaef6 docs: row 273 TOOLS.md activate usage and F39 rules (Filbert N1), BUILD-LOG for rows 202 and 273
TOOLS.md: activate takes --no-sso and --send; three lines state the F39 rules.
BUILD-LOG: the row 202 landing line held since 02:01Z and the row 273 landing line.
2026-10-21 22:37:16 -05:00
jason.woltje 0bbf0f6d18 row 273: T53 test 6 pins the usage line with --no-sso
The usage line gained --no-sso in the proof and sso bracket. T53 test 6
still matched the old bracket and failed the whole run on 978143bf4.
2026-10-21 22:15:36 -05:00
jason.woltje 95f4bd8fdc row 273 notes: name the changed T57 item 3 assertion for review (sage-2321) 2026-10-21 22:15:36 -05:00
jason.woltje d8034c51bc stack-release: a rollback that drops --sso drops --send; the --no-sso walk refuses an ok line with no activation (row 273, sage-2315)
B1: row 200's rollback to a Console without --sso kept the pointer's --send,
which F39 b forbids once --send can sit beside --sso. send_for_target drops it
whenever the run drops the pointer's --sso, and the plan and steps say so.
N1: an ok line with no activation refuses sso-off-flags instead of being
skipped.
2026-10-21 22:15:36 -05:00
jason.woltje b28baf7ee8 stack-release tests: T58 adds sage-2305 cases a and b (row 273)
(a) --sso on, then two re-pins that carry it, then --no-sso gives back the
arrival's ExecStart byte for byte. (b) every set in the log holds --sso, so
--no-sso refuses sso-off-flags before any unit stops.
2026-10-21 22:15:36 -05:00
jason.woltje 3673463182 stack-release: --no-sso drops --send in one place, send_for_target (row 273) 2026-10-21 22:15:36 -05:00
jason.woltje 7991662dc0 stack-release: activate --send and --no-sso (row 273, #1778)
activate --send turns send on only when the run's Console flags hold
--sso, given or carried (F39 b), and puts it after --sso and before the
tester flags. activate --no-sso turns --sso off with the Console flags
from the set of the activation that turned it on, drops --send (F39 a)
and keeps the pointer's tester. Both are usage errors under --units
external. T58 covers the brief's T1-T4; the external test covers T5.
Design and the set-walk choice: agents/darkwing/work/queue-273/BUILD.md.
2026-10-21 22:15:36 -05:00
jason.woltje 378a76b854 queue: revs 3103-3108 (morgana: rows 205 and 274 claims; darkwing: row 273 claim; sage: row 276 F5 follow-up added and briefed (#1781, duckworth-0111 C4), row 202 done after ddfb29af7 (#1700 closed)) 2026-10-21 21:17:02 -05:00
jason.woltje ddfb29af7c row 202: FED R5, mosaic access list, grant and revoke (#1700)
Candidate quackerjack/fed-r5-r5 a74cd9f87 on c3d88840c, manifest of 6
paths (CANDIDATE.sha256). Duckworth APPROVE round 5, rev 3026 (#1700
c30184). C1 gate on Sage's slot by Quackerjack on the merge of a74cd9f87
onto a849d89b7 (3b86180b9), finished 01:56:24Z: cli suite 771/771, skip 0
(run 2; run 1 red on a too-long gate TMPDIR, harness only), every
scripts/test-*.sh rc 0 with Docker up, mutants M1-M13 killed by their
named tests. Landed onto 9c9732983 with the candidate's blobs and modes
for the 6 paths; c3d88840c..9c9732983 touches none of them.
2026-10-21 21:00:28 -05:00
jason.woltje 9c97329833 docs: brief for the row 253 F5 follow-up, supervised legacy AS_USER calls (duckworth-0111 C4) 2026-10-21 20:58:00 -05:00
jason.woltje a849d89b73 docs: phase 2 plan, row 273 cases go in stack-release.test.mjs (mos-1247; test-release.sh never calls stack-release.sh) 2026-10-21 20:23:38 -05:00
jason.woltje 0042f713dc queue: revs 3092-3102 (megavolt: row 236 claim; sage: row 273 activate --send/--no-sso added and briefed (#1778, mos-1246), row 274 FED R3 added and briefed (#1779), row 275 193b1b added and briefed (#1780), row 205 re-pinned to 193b1a with its piece, row 206 re-pinned and after 275) 2026-10-21 20:06:55 -05:00
jason.woltje f0d0b31e9b docs: briefs for the activate --send/--no-sso row, FED R3 and the 193b1 split; phase 2 plan F39 fixes
- docs/plans/2026-10-22_activate-send-nosso.md: the row Mos approved in mos-1246. Darkwing builds,
  Filbert reviews. --no-sso drops --send, --send refuses without --sso (F39), test-release.sh cases.
- docs/plans/2026-10-22_federation-r3.md: FED R3, the broker user principal, on 193b1a's candidate.
  Owner Morgana, reviewer Dewey. Its parents R1 and row 196 have landed, and the filing was owed.
- human-users-build.md: 193b1 split into 193b1a (message filter, chained on 196) and 193b1b (viewer
  verbs and board refusals, after FED R3 and R4). Mutant 3 moves to 193b2 (sage-2288, morgana-0063).
- sso-phase2-window.md draft 2: the tool gap paragraph names the approved row, and rollback step 1
  drops "--send follows the pointer", since --no-sso drops --send (mos-1246 items 2 and 3).
2026-10-21 19:42:01 -05:00
jason.woltje 92be0db025 docs: Q175 phase 2 window plan, draft 2 after mos-1245 (#385, Vikunja 412)
R1 same-copy rollback, 7247d80c7 named, placeholder stop, step 10 aimed at a
tester-owned thread, agent config writes. Names the tool gap: activate has no
--send and no --no-sso, and --expect-from is external-units only.
2026-10-21 19:33:57 -05:00
jason.woltje 02da6a2ae4 queue: revs 3088-3091 (sage: rows 205 and 236 brief re-pins to HEAD and briefed) 2026-10-21 19:30:48 -05:00
jason.woltje 2dd9c80c1c docs: Q175 phase 2 window plan, draft 1 for Mos (#385, Vikunja 412) 2026-10-21 19:21:29 -05:00
jason.woltje 137ddd0079 docs: BUILD-LOG entry for the row 254 landing and a Sage SESSIONS line (#1759) 2026-10-21 19:18:12 -05:00
jason.woltje e3c0446f55 queue: revs 3073-3087 (gizmoduck: row 264 claim, row 268 r2 back to in-progress and in-review on ecd9b35eb, request refused without a token; sage: 268 r2 review request as jarvis, rows 269-272 for 264b-264e added and briefed with issues #1774-#1777; webby: row 268 r2 APPROVE carried on ecd9b35eb) 2026-10-21 19:17:00 -05:00
jason.woltje dcd8d4ddaa queue: revs 3071-3072 (sage: row 254 landing note and done after 331597709 and eb9672c91, #1759 closed) 2026-10-21 18:58:46 -05:00
jason.woltje eb9672c910 row 254: README request-timeout sentence and a DEFERRED entry after the landing (#1759)
Doc-only commit after the row 254 landing. The library README now says
that a sync outlasting the server's 120-second hold gets request-timeout
and still finishes (Dewey N1, #1759 comment 30173; server.mjs sets the
120 s socket timeout and the broker promise runs on). DEFERRED records
Dewey N2: library-mapping-invalid, library-remote-invalid,
library-state-invalid and library-git still map to 502 until the Console
library routes row decides them. No test reads either file's content.
2026-10-21 18:55:26 -05:00
jason.woltje 3315977095 row 254: library.sync 130 s human client timeout, six library-* STATUS entries, channel-proof re-pin, README (#1759)
Candidate megavolt/row254 b31300d79 on c3d88840c, manifest be63cfbe
(7 paths). Dewey APPROVE r1, rev 3006 (#1759 c30173). Gate on Sage's
slot by Megavolt, 23:37:27-23:40:52Z on b31300d79: library 77/77, bus
628/628, webui 305 pass with 1 skip (editor rebuild needs esbuild
0.28.2), every scripts/test-*.sh rc 0; the human-cli timeout and
library-fetch-failed STATUS mutants killed by their behaviour tests.
Landed onto 41b0cf755 with the candidate's blobs and modes for the 7
paths; c3d88840c..41b0cf755 touches none of them. Merged-tree rerun of
library, bus, webui and cli on Sage's slot before the push.
2026-10-21 18:44:22 -05:00
jason.woltje 41b0cf7551 queue: revs 3060-3070 (gizmoduck: row 268 claim and in-review, review request refused without a token; sage: 268 review request as jarvis, notes on 216 and 220 pointing at RULINGS.md da9555ca2, row 264 to gizmoduck with reviewer duckworth and briefed; webby: row 268 r1 APPROVE on 9d9ee12c3) 2026-10-21 18:35:57 -05:00
jason.woltje 5f5b51dd65 sage: rulings for row 264 (T1 item 7 sessions) on Gizmoduck's phase A questions Q1-Q7
Five slices 264a-264e, all MVP; schema label by landing order across rows 236, 264a and 206; PRD 1.13
already landed; row 194 is the stop owner for 264d; one admission hook for row 191; Pi count exact with
Claude unknown until Vikunja 466; legacy single human until row 193 (gizmoduck-0154, #1769, Vikunja 463).
2026-10-21 18:34:12 -05:00
jason.woltje da9555ca2f sage: rulings for rows 216 (C2-05a) and 220 (C2-10a) on the phase A build notes
Row 216: paging approved, get takes universe, links page instead of refusing, manuscript without a
chapter returns the first chapter, long search lines return excerpt null, exclude stays path-prefix
until C2-15, and four codes approved with query-chapter-required dropped. The next round rebases on
row 268's landing and adds the PRD 1.14 playground-level tests.

Row 220: one business helper gives library and creative seat sets from the business files, so the
Discord binding schema doesn't change. M7 is wired in the control board, M8 in discord check and run.
Both fail closed on an unreadable business file. The scope grows by the helper, its test and the board's
serve.mjs. Questions 2 to 4 are answered as proposed.
2026-10-21 18:12:46 -05:00
jason.woltje 0dbb242905 sessions: Ludwig PRD 1.11-1.14 drafts handed to Sage (ludwig-0032), Sage PRD 1.13/1.14 landing (db1e7732a) and rows 264-268 2026-10-21 18:04:09 -05:00
jason.woltje ea6989d4ed queue: revs 3038-3059 (sage: rows 264-268 added for T1 item 7 sessions, C2-09b, C2-10c, C2-15 and C2-01c with #1769-#1773; Case 2 re-pin of 214, 216, 217, 219, 220, 223-227, 229 and 265-267 to rows blob a6ac627c8; 216 and 217 after 268; 268 briefed for Gizmoduck) 2026-10-21 18:03:56 -05:00
jason.woltje db1e7732aa PRD 1.14: playground levels in REQ-LIB-2 (book, universe, draft) and the draft universe in REQ-LIB-3, from Gosalyn's approved ruling 51fdfb7e2 (webby-0089, mos-1233, mos-1241, Vikunja 464) 2026-10-21 17:45:38 -05:00
jason.woltje 5d2437f899 docs: Case 2 design (Gosalyn) as of the playground ruling round d2 at 51fdfb7e2, approved by Webby in 7c00c8345 (webby-0089, mos-1233), landed before PRD 1.14 cites it (ludwig-0031, ludwig-0032, Vikunja 464). The design's history stays on origin/gosalyn/case2-design. 2026-10-21 17:45:38 -05:00
jason.woltje 28e3d82265 gosalyn: Case 2 C2-01c brief round d2 for Webby d1: gate grep scoped to packages and docs/case2 with the six owned hits named, draft frozen with books [], hybrids may have universe-level prefixes, tests for a prefix equal to a book and for prefixes-first order (webby-0090 B1 N1 N2, mos-1240) 2026-10-21 17:45:38 -05:00
jason.woltje 524e169b8c gosalyn: Case 2 C2-01c brief, playground levels: kind values and draft entries, other-universe refusal replacing outside-book, classify checks prefixes first with book null, the readers and every file that pins the old code; C2-05a brief gains one line for the null book and drafts, C2-06 needs none (mos-1240, sage-2218, ruling 51fdfb7e2, Vikunja 464) 2026-10-21 17:45:38 -05:00
jason.woltje 5f73b388c4 PRD 1.13 r2: della-0011 F1 (owner on its own thread and a granted collaborator create or clear a session) and N1 (REQ-SESS-1 title) (Vikunja 463) 2026-10-21 17:38:03 -05:00
jason.woltje 52f36e921e PRD 1.13: session ids from the approved T1 item 7 design (REQ-SESS-1 to 3) and the REQ-ROLE-2 and REQ-THREAD-2 replacements (mos-1231, mos-1235, Vikunja 463) 2026-10-21 17:38:03 -05:00
jason.woltje 6ba8a582e8 plans: row 226 (C2-13b) brief follows design D8: the install makes the stack copy and runs no release package or activate, and the precheck's --library-status reads the bus's recorded sync state after design 10 step 4a (624212842, webby-0085 B1, sage-2189, sage-2195) 2026-10-21 17:36:25 -05:00
jason.woltje aa772d731d plans: Case 2 v1 briefs for C2-09b rooms, C2-10c Console room view and C2-15 hybrid universes, Webby-approved round d2 (Gosalyn 72ceda935 and 99b1e0859 on gosalyn/case2-v1-briefs, webby-0089, mos-1233, Q211, Vikunja 464); codes room-not-open, addressee-ambiguous and R8 accepted by Sage (sage-2218) 2026-10-21 17:36:25 -05:00
jason.woltje 3dcd3651ed docs: T1 item 7 sessions design, approved r3 (Huey; Scrooge scrooge-0008; mos-1232, mos-1235, mos-1236, mos-1237; Vikunja 463)
Lands docs/plans/2026-10-21_t1-sessions.md as of 2c843d190 on origin/huey/t1-sessions-design (347278044, e19e30df2, 2c843d190 on cca206cdc) with its three BUILD-LOG and three SESSIONS append lines, onto the current refactor tip. The design blob is unchanged. Build dependency N2 (Claude completed-compaction format and the managed dataRoot reader) is Vikunja 466.
2026-10-21 17:35:03 -05:00
jason.woltje eb017d77f1 sessions: Dewey 251 r2 and 254 r1, Darkwing re-pin prep, Filbert 257 r2, Sage W9b-W12 and PRD 1.11/1.12 landings 2026-10-21 17:34:17 -05:00
jason.woltje f842462bdd queue: revs 2995-3037 (sage window and review requests; gander 253 start; dewey 251 r2 and 254 r1 records; megavolt 254 start and in-review; gosalyn 226 claim; webby 244 verdict; quackerjack 202 r5; duckworth 0104 record; morgana 257 r1 and r2; filbert 257 r1 and r2 records) 2026-10-21 17:34:02 -05:00
jason.woltje ad9cefcf2d sage: re-pin window 4429a104f to 7247d80c7 record (rows 196, 222, 215, 250, 243, 66): STATE.md, transcript, scripts, S1/S2, matrices, P3 transcripts; W0-W12 passed, pm read open (mos-1226, mos-1228, mos-1230, mos-1234); Vikunja post scripts and records 2026-10-21 17:32:32 -05:00
jason.woltje 5c91c96df5 sessions: Filbert row 257 r1 static review, APPROVE c30188 (rev 3031) 2026-10-21 17:31:06 -05:00
jason.woltje 1509c7cc57 PRD 1.12 r3: mos-1231 rulings (T1.7 is v1 under 463, design 9.2 is an estimate, LIB-6 residual 465, KILL-1 vs Q198 protected mode) and della-0009 owner-thread share condition (Vikunja 462) 2026-10-21 17:19:27 -05:00
jason.woltje 74dea06d92 PRD 1.12 r2: della-0008 F1-F4 (viewer reads, share needs the thread user's grant, Risks line names the sandbox, LIB-6 fixed notices) and the LIB-3 pin wording (Vikunja 462) 2026-10-21 17:17:54 -05:00
jason.woltje 3576afffb3 PRD 1.12: ids for the Q176 items from the approved designs (REQ-USER-3, REQ-USER-4, REQ-LIFE-1, REQ-KILL-1, REQ-LIB-1 to 7), the Risks line and the REQ-DEC-3 title (mos-1229, Vikunja 462) 2026-10-21 17:14:24 -05:00
jason.woltje 73222011f2 PRD 1.11: the review path is Mos's reading of Q211, and the v1 VMs' OIDC provider is open (vikunja:19/461) (mos-1229) 2026-10-21 17:03:11 -05:00
jason.woltje ea0b2a16d6 PRD 1.11: v1 is the 10-27 goal (Q211, mos-1227, Vikunja 459)
Follows Jason's Q211 ruling and Mos's reading. Adds to v1 and removes nothing.
- Status and a Version 1.11 source bullet quoting Q211.
- Users: two v1 users on their own instances (Q176). Where it runs: two PVE VMs, dragon-lin dev only (Q180, Q181).
- Out of scope: minting credentials (Q188, Q190), SSO (Q175) and access beyond localhost (Q176, Q179, Q180) move into v1. The bundled Pocket ID stays out (REQ-INST-2, Q203). Codex and SetSpark stay out. Case 2's own organization is v1.
- REQ-CRED-1: Gitea per seat through a grant (Q190).
- REQ-FED-1: person-to-person v1, agent-to-agent post-v1 (Q179, Q180).
- New REQ-INST-3: one-command install and upgrade, Console unit enabled in both v1 profiles (Q187, Q189).
- Acceptance: the 10-27 goal (Q176). The 1.10 note at :1217 now points to Q211. A Version 1.11 technical note keeps the old list text.
requirements.json goes to 1.11 with 71 ids. Both requirements tests are updated to match.
2026-10-21 17:01:08 -05:00
jason.woltje cca206cdce docs: briefs for Q198 S1 and email rows E01, E03-E07 (post-v1; mos-1222, mos-1223)
E12 and E14 wait to be queued until their WAIT-row and S5 prerequisites exist.
2026-10-21 16:54:13 -05:00
jason.woltje f437973d79 docs: post-v1 email plan (Della, Ludwig approved ludwig-0005; Vikunja 460, mos-1223)
Lands docs/plans/2026-10-21_email-plan.md (merged plan, 399 lines) with
Della's independent plan and her review of Ludwig's plan, from
origin/della/email-plan b0844a5ce on c3d88840c. Della's 10 BUILD-LOG and
4 SESSIONS lines are carried as appended. All rows are post-v1; no live
mailbox, credential, DNS, spend, external mail or re-pin is authorized.
2026-10-21 16:52:52 -05:00
jason.woltje 33029f3a8a deferred: the host pi auth file entry (059577886) moves to Done with row 244 (e69260a3c); real-model suite cases run outside the gate is open (Webby N2 on row 244 r1, #1757) 2026-10-21 16:44:55 -05:00
jason.woltje e69260a3ce row 244: gate runs mount a 0600 fixture pi auth file, test-release activates on the mock adapter, and real-model test-task cases sit behind MOSAIC_LIVE_PROVIDER=1 (#1757)
Candidate morgana/row244 94ab4c2fe on a36523713, manifest 6e7e3e3c (8 paths).
Webby APPROVE r1, rev 3008 (webby-0084, REVIEW-r1.md at webby/reviews
ebb3d83bf; request #1757 c30154). Gate on Sage's slot at 81e037d20
(sage-2147): the five gate-lane files whole 51/0, test-task.sh 91/0,
test-release.sh 14/0, mutants M-G, M-L, M-A, M-T and M-F killed.
94ab4c2fe adds only BUILD.md results. Webby's drift check: a36523713..d37210288
changes none of the 8 paths and no path either suite reads; merge-tree clean.
Rulings A and C (sage-1914, sage-2061). The dropped real-model coverage goes
to DEFERRED in the follow-up doc commit.
2026-10-21 16:44:37 -05:00
jason.woltje 3a853f35dc docs: Q198 system-manager design r6.1 (Huey, Scrooge approved r6 and r6.1, mos-1222)
Integrates origin/huey/system-manager-design 1715efb78 (base 6c75887b0) as one
doc-only commit: docs/plans/2026-10-21_q198-system-manager.md (blob 4fbebd3e,
pinned code evidence base 6c75887b0) plus Huey's 14 BUILD-LOG lines and 7
SESSIONS lines, appended unchanged. Scrooge approved r6 (scrooge-0004) and
confirmed r6.1 N1-N3 closed (scrooge-0005). No package, script, runtime or
queue change. Slices S1-S6 go to the queue under Vikunja 452.
2026-10-21 16:43:25 -05:00
jason.woltje 111ec559c8 brief template: seats hand off directly to the next seat and Sage over T3 (Q210, mos-1221) 2026-10-21 16:36:53 -05:00
jason.woltje 25ffaae832 deferred: T27 sweep test leaves two sleep 60 children; the fix is a process-group kill in both t.after hooks (row 251 landing d37210288, darkwing-0117, Dewey c30168) 2026-10-21 16:36:29 -05:00
jason.woltje d372102882 row 251: T27 adds its key with -t 2 and sleeps 3.5 s, because ssh-agent counts lifetimes in whole seconds (#1756)
Candidate darkwing/row251 27f01ef63 on 3b0aef995, manifest f2bdd684
(BUILD.md and packages/bus/tests/host-run.test.mjs). Dewey APPROVE r2,
rev 2996 (#1756 c30168). Gate on Sage's slot: bus suite whole in a clean
worktree on f7a77721a plus the two manifest paths (b79ad5f2c adds docs only),
627/627 at 21:30:33Z.
Darkwing's slot runs (darkwing-0114) are on the old base and are not the
gate.
2026-10-21 16:30:50 -05:00
jason.woltje b79ad5f2cb brief: the human deletes a creative instance, chained after row 194 (REQ-COS-3, mos-1215); window plan W11 posts on 435 2026-10-21 16:20:12 -05:00
jason.woltje 3fb00018e0 window plan: draft 1 correction, 7333 acceptance was Mos's (mos-1015), Q1/Q3/Q4 answered (mos-1214) 2026-10-21 16:18:35 -05:00
jason.woltje f7a77721a9 window plan: 10-24 re-pin draft 1, NEW 7247d80c7 with --tester-socket (mos-1145, mos-1211) 2026-10-21 16:15:48 -05:00
jason.woltje 829e5f8340 sessions: Sage row 196 landing, rows 253 and 254 launched, 194 b4 ruling, queue revs to 2994, window draft 1 2026-10-21 16:14:06 -05:00
jason.woltje abb352d27e queue: revs 2977-2993; Morgana 244 start, in-review and outcome (2977-2979), Gander 196 r6 manifest (2980/2981), Sage 244 and 196 jarvis review requests, Duckworth 196 m2 approve record (2986), Sage 196 landing note and done, row 254 issue #1759, Darkwing 251 r2 in-progress and in-review (2990/2991), rows 253 and 254 briefed 2026-10-21 16:13:56 -05:00
jason.woltje c3d88840c5 row 196: BUILD-LOG lines and two DEFERRED entries after the landing (#1694)
Doc-only append after 7247d80c7. BUILD-LOG takes Gander's ten row 196 lines from 527b9a1e6 unchanged. DEFERRED records Duckworth's C1 (round 1 N2, the dead testerNoSignIn, binding the next serve.mjs row) and round 2 N5 (the Console library routes row decides on library.sync for the tester listener). No test reads either file's content.
2026-10-21 15:59:48 -05:00
jason.woltje 7247d80c7a row 196: human boundary on peer credentials, interpreter receipt and the Console tester socket (#1694)
Candidate 527b9a1e6 (gander/row196-r6-prd19, base f48725cfc), the 39 manifest paths of CANDIDATE.sha256 537d30aef70b6cd1 at ec1e7fad8. Duckworth approved round 1 outside B1/B2 (rev 2973, comment 30145) and round 2 merge-scoped (rev 2986, comment 30156). Gate gander-0066 on grant sage-2145: 2334 pass, 0 fail, 1 skip; mutants 38/38 killed. BUILD-LOG lines and Duckworth's C1 DEFERRED entry follow as a doc-only append.
2026-10-21 15:55:39 -05:00
jason.woltje 6661732925 sessions: Sage row 222 landing, PRD 1.9 and 1.10, Pocket ID row 255, 196 r6 grants, overlap class rule (mos-1204) 2026-10-21 15:46:03 -05:00
jason.woltje 21def28502 queue: revs 2968-2976; Gander 196 r5 manifest and r6 changes move, Sage 222 review request and done, Duckworth 196 m1 changes record, rows 254 and 255 added 2026-10-21 15:45:45 -05:00
jason.woltje add0f1aae5 PRD 1.10: REQ-INST-2, a bundled Pocket ID for users with no OIDC provider, post-v1 (Q203, mos-1204)
Adds REQ-INST-2 after REQ-INST-1 as drafted in sage-2144 and ruled in mos-1204: Jason's Q203 sentence verbatim, marked post-v1 the way REQ-FED-1 is, with Mos's reading (mos-1202) labelled as his. A Version 1.10 technical note records, as fact, that REQ-WEB-2 uses the Console's own OIDC sign-in while the out-of-scope list still names 'Pocket ID and SSO', which Jason hasn't ruled on (vikunja:19/459). The list and the Pocket ID technical note are unchanged. requirements.json goes to 1.10 with 70 ids, and both requirements tests are updated to match. Design row 255 (#1760, Vikunja 458).
2026-10-21 15:26:45 -05:00
jason.woltje 7d08237e17 brief: bundled Pocket ID for users with no OIDC provider, design (Q203, Vikunja #458, mos-1202) 2026-10-21 15:21:38 -05:00
jason.woltje f48725cfc1 PRD 1.9: REQ-WEB-2, Console decide buttons for a human the Console signed in, and the REQ-DEC-3 amendment (mos-1202)
Jason approved the buttons in decision 00ed58e2 (vikunja:19/266) and chose
O6 in Q203 (OIDC-only), relayed by Mos in mos-1202 with "Land PRD 1.9 with
REQ-WEB-2 as written". The wording is sage-2067's draft. requirements.json
goes to 1.9 with 69 ids; tasks suite 97/0 and bus requirements.test.mjs 3/0
on Sage's slot.
2026-10-21 15:19:43 -05:00
jason.woltje 0de97f429d brief: library sync follow-up, client timeout, STATUS codes and README (row 222 deferrals, #1723) 2026-10-21 15:16:54 -05:00
jason.woltje 0b33f5e9db row 222: C2-03a fetch-only library sync, askpass helper, bus verb and timer (#1723)
Candidate c9aa9c2ee (megavolt, manifest c2121b55, 15 paths), approved by
Dewey (rev 2966, comment 30138). Gate on Sage's slot by Megavolt,
20:08:37Z to 20:11:33Z: library 77/77, bus 609/609, webui library-cli 1/1
and pinned-copy 10/10, cli stack-copy 17/17, every scripts/test-*.sh rc=0,
fixture tokens 0 hits; mutant kills carried from 4a0e0ab3a with equal blobs.
2026-10-21 15:13:31 -05:00
jason.woltje 3586955286 sessions: Sage row 250 landing, 196 r5 run 4 and resume, F27 row 253; Dewey 66 r4, 251 r1 and 222 r1 lines; Darkwing 251 line 2026-10-21 15:04:41 -05:00
jason.woltje a54a17f3d8 queue: revs 2905-2967 (rows 66, 215, 243, 250 landings; 196, 222, 244, 250, 251 rounds; row 253 added)
Includes other seats' revs: dewey 2905 2966; filbert 2906 2924 2950 2957; herb 2917-2919; morgana 2928 2933 2934; gizmoduck 2935 2945 2946 2951-2953; darkwing 2938 2944 2947; webby 2939; megavolt 2960 2961.
2026-10-21 15:04:30 -05:00
jason.woltje 0cdead66cd brief: F27, case2-unit.sh and case1-accept.sh move to --tester-socket, chained after row 196 (#1758)
Mos ruling mos-1198 item 4. Owner Gander, reviewer Duckworth, gate owner Sage.
2026-10-21 14:47:32 -05:00
jason.woltje 1b6158ce26 row 250: K12 holds the stop's release while it reads the shim, the release answers the events read's reason, and R13 closes its controller (#1755)
Candidate gizmoduck/row250-r2 c1a858975 on 3b0aef995, manifest eca0b8e2.
Filbert APPROVE rev 2957 (#1755 c30130). Gate on Sage's slot (grant sage-2109,
gizmoduck-0150): P1/P2/M1 probes as predicted, conversation whole 3x 183/183
and a concurrent pair 183/183 each, control-board whole 124/124.
2026-10-21 14:42:48 -05:00
jason.woltje 081ff6b128 sessions: Sage row 215 landing, 196 r4 grant after the base change, slot order for 250, 222, 244 and 251; Herb row 215 line 2026-10-21 14:35:05 -05:00
jason.woltje be86adbff2 sessions: Filbert row 250 round 2 static review, approve 2026-10-21 14:35:05 -05:00
jason.woltje d163afc47f sessions: Filbert row 250 round 1 static review, changes 2026-10-21 14:35:04 -05:00
jason.woltje e54e054632 row 215: C2-04 the library entry in the business file, consumer skips, M9 organization-projects-protected, the bare mirror, library.file and library.status (#1716)
The business file accepts a kind "library" project entry, and each consumer
that assumed a project root skips it. An organization write whose projects
differ from the file's is refused (M9). The bare mirror is created at the
projectRepo path with no remote. library.file and library.status are human
only and refuse an agent session with human-required.
Candidate herb/row215-on197 dde3445ab on a36523713, Filbert APPROVE rev 2924.
2026-10-21 14:24:37 -05:00
jason.woltje 6096b051b1 row 243: tests that depend on the gate umask set their file modes; stack-instances bootstraps under an explicit umask with a 077 subtest (#1754)
process.test, proof-cli.test and sso.test set the too-open mode with chmod
instead of taking it from the umask. stack-instances.test bootstraps under an
explicit umask and asserts the 0700 config dir under umask 077.
Candidate morgana/row243 6581f750e (code 137bda3af), Webby APPROVE rev 2939.
2026-10-21 14:12:03 -05:00
jason.woltje 9a8c8860d3 sessions: Sage row 66 landing, J2 brief and REQ-WEB-2 wording, gate coordination for 196 r2, 215, 243, 250, 251 2026-10-21 14:06:16 -05:00
jason.woltje 6195a00f8d sessions: Filbert row 215 round 2 merge-scoped review, approve 2026-10-21 13:55:22 -05:00
503 changed files with 46146 additions and 856 deletions
+113
View File
@@ -5177,3 +5177,116 @@ Before: managed project/thread verbs had no Console conversation area. After: fi
2026-10-21T01:28:05Z | Darkwing | row 232 #1734 installer I1 build, round 1 in review | Before: stack-release.sh managed only user units (systemctl --user), and stack-copy.sh had no bundle copy. After: --units external (adopt, activate, rollback --to, status, confirm, revert, gate --since, AWAITING-UNITS, stale-start, external-pm, --plan --expect-from) and stack-copy check --units external, copy --bundle FILE SHA, wrapper ID --create; user mode byte-identical to the base by test. First build 0af0c73c6 on 00d4cc1f6; rebased onto row 200 (cb5fdb064) as a0ffabc3f on darkwing/row232-r1; --sso refused under external (D9), row 200 config_checks run under external (D10). Gate on Sage's slot (sage-1618): cli 561/0, every scripts/test-*.sh green; 17/17 mutants killed. Results commit 84f1a91e6 (pushed, seat branch only). Manifest 7a3b4ea1 (9 paths), queue revs 2595-2596, request comment 29825, reviewer Filbert. Packet agents/darkwing/work/queue-232/BUILD.md.
2026-10-21T04:50:44Z | Darkwing | row 233 #1735 installer I3 build, round 1 in review | Before: root had no upgrade driver for root-owned units and no host run sheet; stack-release.sh had no rehearse --backup last and no external plan refusals. After: scripts/stack-upgrade.sh upgrade and rollback (r6 A1-A5, A2f, R1-R4) with 20 tests, docs/guides/host-run-sheet.md, TOOLS.md lines, and on Sage's rulings stack-release.sh --backup last, BACKUP_STORE and the --plan refusals open-activation and migrate-required (3 tests). Base e65a3ab4d (row 232's landing). Gate on Sage's slot (sage-1704) on d99166ade: cli 616/0, every scripts/test-*.sh green, 1201/0 summed by awk; m1-m8 8/8 killed. Correction: the process sweep after the gate found an orphaned sleep 60 from the lock test's flock holder (N1); 08db6ac3f fixes it test only, Sage ruled the gate stands (sage-1707), rerun c0 20/0 and m1 killed with 0 left (sage-1708). Tip 1a1dc80ca (pushed, seat branch only). Manifest 464ce9eb (7 paths), queue revs 2644 and 2647, request comment 29901, reviewer Filbert. Packet agents/darkwing/work/queue-233/BUILD.md.
2026-10-21T06:37:41Z | Darkwing | row 240 #1746 installer I4 build, round 1 in review | Before: root had no host check for the bwrap AppArmor profile, its pins or a boot unit, and stack-upgrade.sh exited 1 args-failed when the args move after A5 failed, while exit 1 means the old copy runs. After: scripts/case1-hostcheck.sh (seven items, --check, --enforce, --upstream), scripts/case1-pins.sh, mosaic-hostcheck.service and 16 tests; the args move failure exits 5 args-failed, documented in TOOLS.md and run sheet section 6 and tested; run sheet section 3 puts the pinned npm on PATH and names the bin/claude.exe --claude path; sections 8 and 10 have precheck lines. Verification: slot run r1 on 47ec9fe2f failed at the singles (the new N1 test's after hook threw ENOENT after the fixture cleanup removed the root); b45852daa drops the hook; r2 on b45852daa green: singles 16/0 and 21/0, c0 plus M1-M8 8/8 killed, cli 639/0, scripts 585/0. Candidate darkwing/row240-r3 ad1b23ab2 on c771a36f4, manifest 573ece95 (9 paths), revs 2694 and 2695, comment 29943.
2026-10-21T09:00Z | Herb | row 215 C2-04 implementation started | Before: business files reject library entries and root consumers assume code projects; the organization writer can change all project mappings. Plan: approved manifest 2522025af on C2-01 a2c72ac95, Filbert reply to herb-0056, scope sage-1848/1852/1860. Focused slot sage-1869 observed expected red mapping and M9 cases, then 14/0 green for new T1/T2 cases, no skips; clear herb-0062 with empty test/fixture process scan. After that clear, T3/T4 tests and host-only mirror/bus implementation continue under sage-1874, unrun while Sage holds the slot. Full singles, suites and implementation review remain pending. No source commit, push or live change.
2026-10-21T13:29Z | Herb | row 215 C2-04 source verified for candidate review | Before: business code assumed every project had a root and organization writes could replace project mappings. After: library mappings load without agent checkouts or publish grants, project mappings are protected before and after serialization, host-only bare mirrors share a bounded publication lock with sync, and library.file/library.status are human-only async bus reads. Base f8276c5eb preserves all nine landed overlaps. Granted sage-2008 whole singles: 16 files, 451/0, no skips or cancellations, ended 13:27:27.279728Z; all source hashes unchanged, process scan empty, no own-path drift through 8a04f4aa4. Full landing gate and implementation review remain pending; no HTTP, sync writer or live change.
2026-10-20 19:09:44 UTC | Gander row 196 #1694 design phase | Before: tester TCP reaches every local uid and human proofs do not bind the socket peer. After: design draft at agents/gander/work/queue-196/DESIGN.md inventories listeners, proposes private tester HTTP and SO_PEERCRED binding, aligns the per-request principal contract with Morgana and Gizmoduck, and lists migration, tests, mutants and same-uid limits. Python versus other credential options and Console provenance require Duckworth ruling. Documentation only. No product code, suite, live probe, secret access or deployment.
2026-10-20 19:16:20 UTC | Gander row 196 #1694 design r2 | Before: Duckworth r1 requested concurrent helper-fd evidence (B1) and selected O1 with interpreter, proof-only and image conditions. After: design adds eight concurrent genuine proof processes plus a racing copier, M20 swapped socket and post-helper socket survival, root-owned system ELF pinning and activation evidence, safe gid validation, cap traffic without helpers, and unchanged P19 R2 text. Mos accepted socket-only tester access (mos-1015 via sage-1363); any authenticated browser relay is a separate row. Documentation coverage checks passed. No product code, suites or live action.
2026-10-20 19:21:19 UTC | Gander row 196 #1694 design approval and N5 | Before: Duckworth approved r2 (duckworth-0009, cb6d840df) with a nonblocking interpreter-upgrade note. After: r3 chooses startup safety revalidation, appends actual interpreter identity to bus/peercred-starts.jsonl and retains old activation evidence. Operator re-pin uses the next reviewed copy, respecting already-active refusal. Added task-by-task PLAN.md for inline implementation and independent manifest review. Documentation checks passed. No product code, suites or live action.
2026-10-20 19:34:28 UTC | Gander row 196 build paused at design mismatch | Before: approved design promised Unix tester cleanup only for the owned inode. After: scratch test shows Node/libuv close unlinks a replacement inode before JS close handlers. Evidence agents/gander/work/queue-196/evidence/tester-cleanup-blocker.txt. Sent Sage/Duckworth gander-0016/0017, recommending explicit same-uid residual with private 0700 parent retained. Interpreter, concurrent peer, principal, human, peer-transport, transport, tester and process single files passed. Partial code uncommitted, tester transport not wired. No suites, mutant batches or live actions.
2026-10-21 18:45 UTC | Gander row 196 preservation before rebase | Sage-2051, Mos order mos-1193, explicitly authorizes commit and fast-forward push of the current build before deferred full verification. Product and evidence preserved together; prior focused checks and stopped gates are recorded in the row196 packet, with no claim of full acceptance. Next: rebase onto a36523713 on new gander/row196-r2, report range-diff/patch-id/conflicts, then await Sage's clear for M1-M29 and all requested suites. No live action, force push, branch deletion or tests during hold.
2026-10-21T16:34:44Z | Huey | Q198/Q199 system-manager design, mos-1183, Vikunja 452 | Before: pinned foundation 6c75887b0 has separate bus, development queue, run evidence, turn/cohort controls and launch/usage paths; the assignment requests a mechanical manager design. After: docs/plans/2026-10-21_q198-system-manager.md proposes admission/cooldown, priority and safe interruption, durable requests, bounded retry/DLQ/replay/rerouting, Console monitoring, optional decision adapters with USD reservations, six ordered slices and three recommended owner decisions. Verification: 26 pinned file:line references resolve, ten-line summary, manual source/contract review, git diff --check and installed queue guard pass. No builds/suites, runtime writes, credential reads, service changes or paid calls. Huey queue next refused the linked worktree, then returned nothing from canonical checkout; direct brief controls this assignment. Gitea issue and queue-row capture are handed to Sage under the no-credential-read/no-queue-write boundary.
2026-10-21T18:45:47Z | Huey | Q198 system-manager design r2, mos-1194, Vikunja 452 | Before: Scrooge requested changes on add7cb506 for decision-key worker exposure, cap scope and absent sampler ownership. After: distinct MOSAIC_DECISION_OPENROUTER_KEY with S6 sessionEnv canary requirement; exclusive per-instance key with per-key caps and provider limit on every enablement; S2 owns supervisor sampler and waits until after MVP/re-pin. Also records P1 interpretation, missing-confidence fallback and stand-in timeout selection, explains verified Noul spelling, excludes notifier/gateway retry loops with citations, and cites lifecycle revocation requirement. Verification: 30 pinned citation spans resolve, ten-line summary, manual diff/source review and whitespace/queue-guard checks. No builds, suites, live changes, credential reads or paid calls.
2026-10-21T19:56:11Z | Huey | Q198 design r3, mos-1201/1201a, Vikunja 452 | Before: Scrooge approved r2 7e785a08f with three notes. After: N1 names proposed system.health bus read verb and includes broker handler/authorization in S2 scope and review; N2 labels exclusive ownership as a setup attestation that cannot be mechanically proved and identifies provider key limit as real ceiling; N3 adds recommended Q4 for P1 narrowing and removes the Mos-flag instruction. Other design content retained. Verification: manual r2-to-r3 delta review, ten-line summary, whitespace and installed queue-guard checks. No builds, suites, live changes or paid calls.
2026-10-21T20:19:53Z | Huey | Q198 design r4, mos-1203, DECISIONS Q204, Vikunja 452 | Before: r3 a36952564 presented Q1–Q4 for owner approval. After: section 9 records Q20a–Q20c verbatim as approved; D11 labels Huey’s reading of configurable daily/monthly caps with $0.10/$1 defaults in the one instance config via reviewed schema; provider key ceiling and explicit enable/key requirements retained; Q4 pending, Jason restated 2026-10-21, P1 unchanged. Verification: quotes match DECISIONS ledger, unrelated sections and P1 equal r3, ten-line summary, whitespace and installed queue guard checked. No builds, suites, live changes or paid calls.
2026-10-21T20:48:49Z | Huey | Q198 design r5, mos-1206, DECISIONS Q205/Q206, Vikunja 452 | Before: r4 1e8bccff1 left Q4 pending and long outages could exhaust request retry budgets. After: protected mode separate from unchanged D1 human kill authority; mechanical detection, safe-point pause/proof, JEV-first endpoint-before-headless probes at 60-second increments to 600 seconds without attempt limit, upstream reset waits, shared D11 probe accounting, one test session then batches, proposed three-failed-exit escalation, frozen outage clocks/counters without DLQ, advisory online-service routing, Q4 superseded and Q22 pending. Thresholds/deadlines/batches/escalation explicitly labelled Huey readings. Acceptance proposals updated, no implementation. Verification: complete Q205/Q206 read, quotes match ledger, D1/inventory/Q1–Q3 unchanged, 30 pinned citation references resolve, 11-line summary, manual consistency/diff review, whitespace and installed queue guard checked. No builds, suites, real probes, live changes or paid calls.
2026-10-21T21:04:37Z | Huey | Q198 design r6, mos-1208/mos-1210, DECISIONS Q207, scrooge-0003, Vikunja 452 | Before: r5 fd44f7c60 gated probes on JEV and lacked request-local resume failure separation. After: parallel independent probes; central JEV/smaller-model/rules endpoint chain, per-model timeout/calibration, 4 KiB metadata limit and isolated fallback credentials; subscription quota vs metered USD accounting and separately enabled paid-probe budget (proposed $0 defaults). S-F1–S-F7: bounded outage classification, request-local D6/two-resume DLQ rule and batch definition; escalation latch with authorized revision-bound system.recovery.release separate from kill clear; cooperative F5 pause/F6 proof only; proposed configurable 24-hour cumulative pause expiry; full Q205 kill sentence with labelled protected-mode interpretation; r3–r6 history. S5/S6 acceptance proposals updated. Verification: Q205/Q207 quotes match ledger, D1/inventory/Q1–Q3 unchanged, 30 pinned references resolve, summary within 15 lines, manual consistency/delta review, whitespace and installed queue guard checked. No builds, suites, real probes, live changes, credential reads or paid calls.
2026-10-21T21:39:25Z | Huey | Q198 r6.1 touch-up, mos-1222, scrooge-0004 N1–N3, Vikunja 452 | Before: approved r6 2ee68cd69 had unprovable session-call denial wording, implicit queue/late-call behavior and a partial Q207 paragraph. After: rule/S6 tests require session decision calls/output have no routing authority; non-waiting dispatchers use internal rules immediately; timed-out JEV does not occupy fallback decision slot but retains spend reservation; full Q207 paragraph copied exactly from DECISIONS; matching Q22 reading corrected. ENV_ALLOW and all runtime code unchanged. Verification: exact full-quote equality, obsolete wording absent, D1 unchanged, summary within 15 lines, manual N1–N3 delta review, whitespace and installed queue guard checked. No builds, suites, probes, live changes or paid calls. Review/handoff via T3 as Mos authorized.
2026-10-21T21:11:44Z | Della mos-1213 / Vikunja 460 planning phase | Before: PRD 1.10 has no email feature; MP 95b0298 and EP 7a64921 are read-only prior inputs. Scope: independent post-v1 email plan on della/email-plan, no implementation/live access/MVP or queue changes. Gitea issue enrollment belongs to Mos/Sage because this seat has no API credential.
2026-10-21T21:12:18Z | Della mos-1213 / Vikunja 460 planning verification | After: docs/plans/2026-10-21_email-plan-della.md is 341 lines, with 13 post-v1 rows and six Jason questions. Verified 42 pinned citation anchors, ten requested coverage areas, identical prior src trees, append-only records, clean whitespace and repository queue guard. No configured CI pipeline or CI-queue wrapper found in the current foundation; documentation-only validation applies, no product suites or live tests run. Phase B/C and Gitea issue/queue enrollment remain with Mos/Sage. Only della/email-plan is authorized for push.
2026-10-21T21:37:07Z | Della mos-1220 / Vikunja 460 phase B cross-review | Before: independent plans Della 191dff32f and Ludwig 4a27b73ac delivered. After: docs/plans/2026-10-21_email-review-by-della.md reviews all 218 Ludwig plan lines, records ten findings, five grafts, six retained strengths, nine disagreements and eleven merged Jason questions. Ludwig review 143415779 read in full after ludwig-0002 arrived; reconciliation records new mail-access action, no agent reroute and correction of Della dataRoot credential example. Original plan unchanged. Planning only, no product/MVP/queue/live change.
2026-10-21T21:43:02Z | Della mos-1223 / Vikunja 460 phase C synthesis | Before: independent plans/reviews complete, Mos selected Della base. After: docs/plans/2026-10-21_email-plan.md merges Ludwig grafts and accepted corrections, with 16 post-v1 rows (eight READY, eight WAIT/optional), eleven deduplicated Jason questions, per-row test paths and explicit Q210 non-v1 scheduling. Original Della plan/review preserved; Ludwig originals remain on their branch. Static checks cover graft/row/question/citation anchors and original blobs. No source code, suite, mailbox, secret file, service, MVP or queue change. Candidate goes to Ludwig before Sage landing/queue handoff, copying Mos.
2026-10-21T21:45:20Z | Della mos-1223 / ludwig-0004 focused corrections | Before: b096a16cc left content-key location implicit and E05 core registration marked ready. After: merged plan 399 lines fixes external configDir secret key/lifecycle namespace outside dataRoot backup/restore, E01 schema and E03 missing/expired-key restore tests, and E05/E06 test-local registry readiness with core kinds.mjs registration gated on approved E01/Q4. Sixteen rows and eleven questions retained; originals unchanged. Focused static checks and queue guard apply, no product/live changes. Reviewer will check changed lines only.
2026-10-21T22:24:12Z | Huey | T1 item 7 sessions design r1, mos-1232, Vikunja 463 | Before: origin/refactor cca206cdc keys assistant claims/stores/messages by thread and renders Pi compaction markers; row 194 r4 excludes thread context clear. After: docs/plans/2026-10-21_t1-sessions.md proposes independent per-agent sessions within threads on Pi/Claude, scoped claims/stores/messages/workspaces, completed compaction counts, owner-write/RBAC matrix, idempotent proven-stop/fresh-successor clear, common admission/kill checks, migration and seven acceptance groups, replacement REQ-ROLE-2/REQ-THREAD-2 text and three owner questions. PRD 1.12 USER-3/4 candidate 1509c7cc5 cited separately from base. Verification: 22 pinned references resolve, eight-line summary and three exact owner quotes; manual source/consistency review, whitespace and installed queue guard checked. No code, queue/PRD edits, builds, suites, credential reads, live changes or paid calls. MVP priority ahead of Q198 S1. Review via Scrooge, then Sage/Ludwig as Mos authorized.
2026-10-21T22:28:56Z | Huey | T1 sessions design r2, scrooge-0006, mos-1235/1236, Vikunja 463 | Before: r1 347278044 lacked the exact per-session checkout/single-writer invariant and safe runner drain ordering, and admitted other role instances. After: F1 exact workspace path, fenced checkout setup, legacy shared-workspace quiescence, clear retains old dirty/untracked/unpushed checkout and creates fresh approved checkout, T2 mutant; F2 dequeue fence and generation-bound turn-boundary idle proof before runner signal, busy drain stays clearing, T4 cases; F3 THREAD_DEFINITION only per Mos scope ruling, no role eligibility change; N1 private titles/history vs owner-visible count/activity projection; N2 Claude offline adapter/managed-store-reader dependency flagged as MVP risk. Section 8 records Mos’s Q1–Q3 readings, no questions to Jason. Verification: 27 pinned references resolve, summary within 15 lines, source/delta consistency review, whitespace and installed queue guard checked. No code, queue/PRD edits, builds/suites/live changes/provider calls/credential reads.
2026-10-21T22:30:20Z | Huey | T1 sessions design r3, scrooge-0007 R1, Vikunja 463 | Before: r2 e19e30df2 nested new session checkouts under the retained legacy instance workspace, allowing project-name collision or historical parent inspection to reach live siblings. After: exact path is dataRoot/session-workspaces/business/instance/session, outside the legacy root; canonicalized ancestor/descendant overlap refuses; T2 nesting mutant covers historical mount/inspection and a legacy project named sessions. All other r2 content retained as approved, revision label updated. Verification: old nested path absent, manual R1-only delta, whitespace and installed queue guard checked; no code/builds/suites/live changes/provider calls/credential reads.
2026-10-20 18:51:10 UTC | Row 191 #1689 design phase | Before: per-business launch revoke and per-binding Discord STOP leave task containers, active sessions and background work without one switch. Proposed per-data-root durable halt with human-only resume, generation-fenced starts, bounded concurrent termination, write-once stop receipts and existing event mirror; no schema migration. DESIGN.md inventories starter/continuation paths and container control-mount restriction. Sent to Dewey with Sage copied; implementation waits independent design approval. No tests, commits or live actions.
2026-10-20 18:57:51 UTC | Row 191 design revision | Dewey r1 changes: interim Console --send absence, reserved processing events and missing-state semantics required correction. DESIGN-r2.md now names CLI interim path, unavailable UI controls, trusted initialization and fail-closed missing leaf, container delete/replace mount tests, reservations and id-bound reconciliation. Small notes addressed including diagnostic seats and aliases. Recommended smaller Docker scope to Sage; design and scope approval pending, no code or test runs.
2026-10-20T19:50:56Z | Rocko | row 191 #1689 implementation stage: from independent brakes to shared root state, generation-fenced launcher and tracked stops, Discord/task guards and control HTTP/UI. Evidence is agents/rocko/work/queue-191/logs/ and PROGRESS.md. Fixture single files only so far; heavy verification and candidate not complete. Scope additions sage-1367 (one read-only CODE file) and sage-1392 (processing-only client socket timeout); no schema, proof baseline, live file/unit, commit or push change.
2026-10-20 22:58:45 UTC | Rocko | row 191 #1689 rebase inspection candidate | Rebuilt uncommitted kill-switch tree from da230ca48 onto landed row 190 base 00d4cc1f6 under new branch rocko/row191-r2-00d4. Hand merges retained instance flags and SSO/sign-out changes; fixture tests exposed and fixed SSO signer compatibility and two unknown-runtime cleanup defects. Final touched singles 249/249 across 26 files; 15 overlapping Client reader files 102/102. Manifest a4f780c2 has 78 paths; packet 185 files verified with scan clear. Mutants and owner gate remain pending; no review-ready or activation claim, no commit/push/live action.
2026-10-21 23:39:21 UTC | Rocko | row 191 #1689 inspection snapshot | Sage-2242 authorized a local inspection commit for Honker's row 194 S6. Created d52b4db1c on new rocko/row191-inspection-20261021, parent 00d4cc1f6, preserving the reviewed-design build and candidate a4f780c2 (78 paths). Static manifest/index/source scan and diff check verified; no tests run, no push credential or push attempt. Existing single-file evidence is historical, 249/249 and overlapping Client readers 102/102; suites/mutants/gate remain pending. Current code has verified runtime signalling and instance halt, but no dedicated per-run stop API yet; Honker and Sage notified of that seam directly. Snapshot is not review-ready or approved for live use.
2026-10-22 00:04:05 UTC | Rocko | row 191 #1689 per-run stop inspection | Implemented agreed Honker-0079/0081 stopRun contract with verified per-signal identity, fixed 5s+2s, durable first-owner join and immutable result plus later-exit observations. Focused CLEAR sage-2250: final 9 files 62/62, no skipped, after correcting host guard placement and peer-discovery fixture timeout isolation; initial failures and failed teardown logs retained, only own failed group stopped. Slot returned with cwd sweep clear. Sage published f7bf7dfec; sage-2265 clean rebase creates 7aa18b0f6 on dcd8d4dda under new branch, explicit Rocko identity, all 79 manifest product blobs identical so 62/62 evidence carried. Direct S6 handoff sent to Honker and Sage. Wider suites, mutants, review and activation remain pending.
2026-10-22T00:18:12Z | Sage (Opus 5.5) | row 254 landing (#1759), library.sync human timeout | Before: the human transport gave library.sync the 5-second default, so a sync longer than that reported outcome-unknown, and six library-* codes reached the Console as 502. After: 331597709 lands Megavolt's candidate (7 paths, tree d49e4626f, manifest digest be63cfbe recomputed and matched). human-cli.mjs waits 130 s for library.sync, past the server's 120 s hold, and 5 s for other verbs. webui bus.mjs maps library-sync-busy, library-diverged and library-accept-stale to 409, library-accept-invalid to 400, and library-credential-missing and library-fetch-failed to 503. channel-proof re-pinned. Merged-tree gate on the landing commit: library 77/0, bus 628/0, webui 305/0 (1 skip), cli 749/0 (23:44:33-23:55:11Z). eb9672c91 adds the README request-timeout sentence (Dewey N1) and a DEFERRED entry for the four library codes still at 502 (Dewey N2, binds the Console library routes row). Overlap sweep before push: 268 rebased to r2 ecd9b35eb (README only, approval carried webby-0096), 194 rebased to b7. Done rev 3072, #1759 closed.
2026-10-22 00:24:34 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 full gate on c78f48946 | stopped on second integration failure per sage-2275; singles 269/269, processing-control 61/61, bus 646/646; CLI interrupted with 26 observed failures; no further edits; cwd sweep empty and slot returned to Sage.
2026-10-22 00:48:20 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 eb3abba12 full gate | stopped at Discord 190/192; 32 singles 304/304, processing-control 61/61, bus 646/646, cli 761/761; two uninitialized Discord fixtures reported to Sage; no later run; cwd sweep empty, slot clear.
2026-10-22 00:58:27 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 sage-2294 fixture fix | e6800380d on eb3abba12, two test files/four additions, assertions preserved; 338-entry static all-package sweep recorded; no tests; sent Sage for publication and Honker for dependent gate.
2026-10-22 01:30:04 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 e6800380d resume | binding15/15, recover5/5, Discord192/192; stopped WebUI314 passed/4 failed/1 esbuild skip; processing polling violates sign-out/manual redirect and bar changes layout; no fixes/retries, cwd sweep empty, slot returned.
2026-10-22 01:38:08 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 sage-2317 Console integration fix | 8e1f55c1d on e6800380d; shared sign-out/manual redirect/interval and desktop frame fixes, assertions retained, six browser cases, 73 mutant sites preflight; syntax only, no tests; sent Sage/Honker.
2026-10-22 02:06:38 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 rebase sage-2329 | eafcbd3e7 on ddfb29af7, new branch, dispatcher retains processing and access; CLI/readme auto-merges checked; 87-path fd0f2740 manifest and 73-site preflight; Sage2333 published, awaiting CLEAR; no tests.
2026-10-22 02:20:51 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 eafcbd3e7 full gate | stopped singles313/6: six new browser cases count app freshness timer as processing timer; diagnosis sent Sage, no fix/retry; no suites/shells/mutants; cwd sweep empty, slot clear.
2026-10-22 02:23:13 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 sage-2345 timer fixture | 4db8b5778 on eafcbd3e7, one line attributes timer to processing.js; all assertions retained, no product change or tests; sent Sage and Honker; awaiting publication/CLEAR.
2026-10-22 02:25:29 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 sage-2349 record commit | 0f8235277 on 4db8b5778 adds packet BUILD.md cause record; product manifest remains ffb46fbd/87 paths; no amend, product change or tests; Sage/Honker notified.
2026-10-22 02:48:36 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 0f8235277 full gate second red | singles330/330, processing61/61, bus646/646, cli783/783, discord192/192; WebUI323/1/1 host esbuild skip; shell geometry1062vs1000; stopped/no retry, no later suites/shells/mutants; cwd sweep empty, slot returned, Sage escalates Mos.
2026-10-22 02:57:36 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 #1689 mos-1248 desktop layout restart | FF 5cd32f4c1 on 0f8235277: desktop grid allocates processing bar, existing shell assertions kept, visibility/no-scroll assertions added; 74 static mutant sites; candidate 2f93ef19; sent Webby/Sage for static review; no runs, awaiting review and CLEAR.
2026-10-22 03:11:09 UTC | Rocko (Codex gpt-6.1-sol, d99326cc) | row 191 static nav layout, mos-1249/1250 | FF da4334806 on 5cd32f4c1; controls first in nav, non-sticky per sage-2386/webby-0108; candidate-only placement assertion changed, landed shell assertions preserved; 77 mutants statically preflighted; manifest 245dceb1; Webby/Sage handoff sent, no runs, awaiting static look/publish/debug CLEAR.
2026-10-22 03:15:11 UTC | Rocko (d99326cc) | row 191 sage-2394 debug | da4334806 shell5/5 and processing-browser9/9, awk14/0/0; geometry heights reported with read-only distinction; cwd sweep[] and slot returned; full window awaits separate CLEAR.
2026-10-22 02:01:30 UTC | Sage (Claude Opus 5.5, 1ef1e4f8) | row 202 #1700 FED R5 landing | ddfb29af7 on 9c9732983: 6 manifest paths from a74cd9f87 (digests match CANDIDATE.sha256, patch-id 9d89077b equal); Duckworth APPROVE r5 rev 3026; C1 gate by Quackerjack on merge 3b86180b9: cli 771/771 skip 0 (run 2, run 1 red on gate TMPDIR length), 10 test-*.sh rc 0, M1-M13 killed; done rev 3108; landing note refused for length before the done move, so the record is the commit body and #1700 c30265; 191 and 194 rebased onto it (one scripts/mosaic case line)
2026-10-22 03:35:55 UTC | Sage (Claude Opus 5.5, 1ef1e4f8) | row 273 #1778 activate --send/--no-sso landing | 0bbf0f6d1 landed on 378a76b85, a rebase of cac7d0f67 (0042f713d) with all 6 range-diff pairs = and equal patch-ids; 4-path manifest e1031023 unchanged; Filbert APPROVE rev 3111 (c30308) carried merge-scoped under mos-1160, carry comment c30319 names 0bbf0f6d1. Gate on 0bbf0f6d1 by Darkwing: stack-release.test.mjs 218/218 skip 0, stack-release-external.test.mjs 25/25 skip 0, test-release.sh rc 0 (selftest 14/0, no skips), T58 controls c0 11/11 pass and c1 11/11 fail, M1-M7 killed. Rerun because row 202 changed scripts/mosaic, which stack-release.test.mjs copies. Red 1 was 978143bf4 (T53 test 6 usage pin). This doc commit adds the TOOLS.md usage line and F39 rules (Filbert N1).
2026-10-22 04:02:12 UTC | Rocko (d99326cc) | row191 full window0fa7bf421 | touched35files336pass, packages2403pass/1esbuildskip, shells425selftests+156queue node pass; SR01–07killed, SR08timeout,69unrun; stopped/no retry;88blobs restored,cwd sweep[],owned network removed,slot returned Sage; await Mos ruling.
2026-10-22 04:14:56 UTC | Rocko (d99326cc) | row191 control/resume sage-2427 | control12/12;SR08separate timeoutkill;49resumed assertionkills thenKS43survived;total56assertionkills+1timeout,1survivor,19unrun;88blobs restored,cwd sweep[],slot returned,Sage/Mos informed;no retry/edit.
2026-10-22 04:21:30 UTC | Rocko (d99326cc) | row191 KS43+19 retarget run | KS43assertionkill thenKS44invalid mutation syntax;57assertionkills+1timeout,1invalid,18unrun;stopped,no edit/retry;88hashes restored,cwd sweep[],slot returned,Sage/Mos notified.
2026-10-22 04:30:06 UTC | Rocko (d99326cc) | row191 KS44+18 resume | 10assertionkills thenKS54timeout-without-control despite dependency assertion;total67assertionkills+1timeoutkill,1unqualifiedtimeout,8unrun;88hashes restored,cwd sweep[],slot returned,Sage/Mos notified,no retry/edit.
2026-10-22 04:41:46 UTC | Rocko (Codex gpt-6.1-sol,d99326cc) | row191 #1689 builder complete/in-review | published0fa7bf421,manifest0bcbf337;35singles336pass,9packages2403pass/1knownskip,10shells425selftests+156queue nodepass;75assertionkills+2controlledtimeoutkills=77;88hashrestore,cwd sweep[],slotreturned;in-review rev3114,expected no-token failure3115(exit1);Sage/Dewey handoff,no live action.
2026-10-22 05:03:18 UTC | Rocko (Codex gpt-6.1-sol,d99326cc) | Vikunja502 processing test/runner hardening | brief+isolatedrocko-v502;expected wordingred→green,Node27/27,Python4/4,5/5assertionkills(includingSR08andKS54nohang),0skips/timeouts;sweep[],candidate5hashrestored,slotreturned05:01:29Z;candidate968d6ee3,runnerc5bdc764;packetready,no commit/push/liveaction.
2026-10-22 05:07:34 UTC | Rocko (d99326cc) | Vikunja502 committed/review handoff sage-2482 | newbranchrocko/vikunja502-ce6a-20261022,source8e3c6a79b+packet9bf4683c6 on ce6a96ad6;5paths no base drift,range-diff=,patch-id37135ebe unchanged;manifest968d6ee3,packet93889519 verifies39/39;focused runs carry;Sage publish/Dewey review handoff sent,no push/liveaction.
2026-10-22 05:10:00 UTC | Sage (1ef1e4f8) | serial landing 205 then 268 | 205: 3 commits, 4 paths, digest 858463b1, every scripts/test-*.sh green on ce6a96ad6, Duckworth N1-N4 carried (N1/N3 to 205b or R3, N2 to the 193b2 brief, N4 #1782 before any user thread goes live). 268 r4: c27207f6e on 205, 9 paths, digest caa0fa4e, patch-id 2942acc8 equal to r3. Overlap checks clean before both pushes. No live action; LIVE stays 86f95bb27.
2026-10-22 05:58:59 UTC | Rocko | row 279 #1784: before, deadline overshoot skipped final observation and returned unknown; after, one-line for (;;) retains final mapping and unchanged unreachable fallback. Regression red as specified; whole stop-run 13/13 and processing-control 62/62; M1 assertion kill, no skips, restored and swept. Source 1016b8584, packet cad927137; manifest 312ed511d8f1cff826ee2fd547de8368923130c7bfe06236d8e29d8655b80aaa. Pending Sage publication and Dewey review; no live action.
2026-10-22 06:15:54 UTC | Sage (1ef1e4f8) | row 279 #1784 landed (mos-1269) | Before: stopRun returned unknown for a live run when a sleep overshot the deadline. After: LANDED ceadad993 as a carry of cad927137 onto 343b6dd76 (range-diff 1:= 2:=, patch-ids 6d8ae7f4/e484c297 equal, Dewey carry c30410). Sage gate on cad927137: business 209/0/0, cli 794/0/0, discord processing + processing-stop 9/0/0. Correction: the first discord run wrote to a wrong out path and ran nothing; the rerun is the result. Overlap sweep clean (13, 257). Note rev 3144, done rev 3145. No live action; LIVE stays 86f95bb27.
2026-10-22 06:37:43 UTC | Sage (1ef1e4f8) | row 194 b16 third red (mos-1269/mos-1270) | Gate on ceadad993 + 9cdc47d04: business 228/0, cli 826/0 (T9a passes), control-board 124/0, bus 705/1 at grant-executor.test.mjs G13 (timed out 10044 ms); conversation, webui and scripts/test-*.sh never ran. runb8 green 436/0/0, 17/17 mutants. Correction to sage-2524 (mos-1270): the stuck wait is the first until at :733, the holding file after decision.resolve, so the executor never started the install and hosts.revoke at :734 never ran; it is not a revoke-path hang. Ruling: a diagnosis run, 30 base + 30 candidate alternating under cli load, no gate retry.
2026-10-22 07:51:04 UTC | Rocko | row 280 #1785: before, any TCP connect could accept a port our sshd failed to bind; after, attempt-local exact log proof plus liveness and connect, non-ephemeral picker, bounded scratch remote sleep cleanup. Both occupied regressions red on old acceptance; CRLF build failure captured and fixed under sage-2535. Committed source b765f5cb8, packet d6c924764, manifest27806c0579c76d0999d2e6be4180c3867570df0268af9d7e0d7b971af799e6f5. Repeats07:29:03Z–07:48:38Z grant1230/1230,host780/780,CLI1588/1588; final M1/M2 four assertion kills,0skips/timeouts,sweepclear. Sage publishes/gates; Duckworth reviews.
2026-10-22 08:00:02 UTC | Sage (1ef1e4f8) | row 280 #1785 landed | before: the bus tests' scratch sshd helper returned a port on a connect alone, and 194 b16's gate hung at G13 (base hung too, honker-0127). After: b0fd28b6f on refactor, a ff from da5d3651d (b765f5cb8 + Rocko slim packet; full evidence d6c924764 on rocko/row280-da5d-20261022). Duckworth approve rev 3153 (manifest 27806c05). Sage gate bus 656/0/0, ge 41/0/0, hr 26/0/0 (c30431). Rocko repeat 2010/2010 under cli 1588/1588, no timeout; M1/M2 killed 4/4. Two build-time slips recorded in Rocko's packet: a runner root path (no test ran) and the CRLF sshd log line. Overlap 13/257 clean. Note rev 3154, done 3155, #1785 closed c30432, P19 511 comment 623. 194 b17 rebases on b0fd28b6f (sage-2542).
2026-10-22 08:38:11 UTC | Sage (1ef1e4f8) | row 194 #1692 landed | before: 194 b16 9cdc47d04 went red three times (stopRun deadline, then the bus test sshd port race), fixed by rows 279 and 280. After: fd6dd327c on refactor, a ff on e122aad8e. b18 is b17 3f451772d rebased after my doc and queue push blocked a ff (16 =, series patch-id 64303253f8e6, packages equal b17). Webby approve rev 3160 on manifest 29f131ab. Honker gate on fd6dd327c: runb8 503/0/0, mutants 17/17; 18 packages 3668/0, ten scripts rc=0, Docker up, env skips named. The secret scan hit three fake_*_token fixtures in bus tests (false positive). Note 3161, done 3162; #1692 closed c30435; P19 414 comment 625. LIVE still 86f95bb27.
+1
View File
@@ -22,6 +22,7 @@ RUN npm ci --ignore-scripts
# runtime adapters.
COPY contracts /opt/mosaic/contracts
COPY src /opt/mosaic/src
COPY packages/processing-control/src/state.mjs packages/processing-control/src/entrypoint.mjs /opt/mosaic/processing-control/
COPY adapters /opt/mosaic/adapters
RUN chmod 0555 /opt/mosaic/contracts /opt/mosaic/contracts/* \
&& chmod 0555 /opt/mosaic/src /opt/mosaic/src/*.sh \
+191
View File
@@ -0,0 +1,191 @@
# Row 251 build: T27 lists no identities right after `ssh-add -t 1` (Vikunja #453)
Brief `docs/plans/2026-10-21_t27-ssh-add-lifetime.md` (rev 2931), Mos ruling mos-1185 item 2, assigned in
sage-2075. Owner Darkwing, reviewer Dewey, gate owner Sage. Branch `darkwing/row251` from 3b0aef995,
claimed in-progress at rev 2938.
## Step 1: the failure names its cause (e5e4b2e5a)
T27's two lists now carry a message that prints only when they fail. It gives the time from addKey's
start to its resolve and to each `ssh-add -l` call, the hrtime at each with how far into its second it
fell, `/proc/uptime` at each, and whether the agent process and its socket were still there. The checks
themselves did not change. The commit holds nothing else.
## Step 2: the cause
ssh-agent counts a key's lifetime in whole seconds of CLOCK_BOOTTIME, at the add and at the reap. I read
OpenSSH 10.5p1, the version on dragon-lin (`ssh -V`: OpenSSH_10.5p1), at tag V_10_5_P1 (ssh-agent.c
sha256 873691c4, misc.c sha256 ee467f43):
- `misc.c:1790-1798` and `:1828`: `monotime()` returns `ts.tv_sec` of `clock_gettime(CLOCK_BOOTTIME)`,
falling back to MONOTONIC. The fraction of the second is dropped.
- `ssh-agent.c:1315`: the `-t` constraint sets `*deathp = monotime() + seconds`. Lines 1410 and 1598
do the same for the agent's default lifetime.
- `ssh-agent.c:1064`: the reaper drops a key when `now >= id->death`, `now` being `monotime()` again.
- `ssh-agent.c:2650`: the main loop runs `reaper()` after every poll, before it reads requests. A list
that arrives after the boundary sees the key gone.
So a key added at second s plus a fraction f dies at s + N, and `-t N` keeps it N - f seconds, between
N - 1 and N. With `-t 1`, an add late in a second leaves the key milliseconds to live. That matches
Sage's probe in the brief: 8 of 80 `-t 1` adds missed, each one made at 0.9947-0.9973 of a monotonic
second and finished at 0.998-0.0008, and none earlier in the second missed. It also matches proof 5's
failure, where the first list ran 31.9 ms into the test and found no identities.
Node's hrtime is CLOCK_MONOTONIC. BOOTTIME differs from it by the time the host spent suspended. On
dragon-lin the difference read 0.000001 s at 19:09Z, so a monotonic second boundary is a BOOTTIME one
here.
`addKey` and the agent start are not the cause. `addKey` passes the lifetime through as `-t`
(`host-run.mjs:90`), and the agent starts with no default lifetime (`:175`). Production adds the key
with `timeout + 30` (`:180`), where losing up to a second makes no difference. The only caller that
can't afford the lost fraction is T27, which asks for 1 s. The fix belongs in the test's lifetime choice,
and `host-run.mjs` doesn't change.
The slot confirms this against T27 itself (below), not just from the source.
## Step 3: the fix (this commit)
T27 adds the key with `-t 2`. The agent then keeps it more than 1 s and at most 2 s. The first list
runs milliseconds after the add, so it has over a second of margin even under load. The sleep before
the second list grows from 2.5 s to 3.5 s, which keeps the old 1.5 s margin past the new 2 s ceiling.
Both checks stay: listed after the add, gone after the lifetime. A comment above the add says why the
lifetime is 2.
I considered `-t 3` with a 4.5 s sleep for a 2 s floor. One second of floor is far above anything the
first list has needed (31.9 ms in proof 5), and the concurrent-suite run below tests it under load, so I
took the smaller change.
## Base
The branch stays on 3b0aef995. By 19:15Z origin/refactor was at 6096b051b, after rows 66 and 243
landed. `git diff 3b0aef995 6096b051b` touches neither `packages/bus/src/host-run.mjs` nor
`packages/bus/tests/host-run.test.mjs`, so there's no rebase (sage-2084). Row 243 did change
`packages/bus/tests/process.test.mjs`. My slot's bus suites run that file as it was at 3b0aef995, and the
gate rerun on Sage's slot runs it as landed.
## Prediction for part A on e5e4b2e5a (written before the slot)
The hook starts `ssh-add -t 1` at the given fraction of a monotonic second. From the brief's probe, the
agent stores the key about 3 ms later. The first list reaches the agent a few ms after addKey resolves:
Node's exit event, the mark, and `ssh-add -l`'s own start. A red needs the add to land before the second
boundary and the list to land after it.
- 0.990: green. The add and the list both land before the boundary.
- 0.993, 0.995, 0.996, 0.997: red expected. The add lands before the boundary, the list after. At 0.993
and 0.997 I'm less sure, because the list (at 0.993) or the add (at 0.997) can land right at the
boundary. A green at either is in line with the cause, and so is a red.
- 0.998: green. The add lands after the boundary, so the key gets almost a full second.
Every red should show "first list got" an answer with no SHA256 line. It should also show addKey
resolving before a second boundary and the first list starting after it or within a few ms of it, with
the agent alive and its socket present.
Under sage-2084's rule, on e5e4b2e5a only a red at 0.990 or 0.998 counts toward the stop, and two such
reds stop the run. On the fix commit any red counts, and two stop it. In parts B to D, the first red stops
the run.
## Slot plan
All scripts are in `/mnt/storage/scratch/darkwing-251-out/`, launched from `/`, each behind the busy
checks and chained with `&&`, TMPDIR under `/mnt/storage/scratch/tmp`.
1. Confirmation. `align-hooks.mjs` is a load hook, outside the repo, that serves `host-run.mjs` with
`addKey` wrapped to start `ssh-add` at a chosen fraction of a monotonic second. T27 runs unchanged
under it, alone (`--test-name-pattern`). It runs at fractions 0.990, 0.993, 0.995, 0.996, 0.997 and
0.998, three times each:
- on e5e4b2e5a (step 1, `-t 1`). Expected: some first lists fail, and the step 1 message shows the
add's resolve and the list on either side of a second boundary, with the agent alive and the
socket present. These reds are the point of the run.
- on the fix commit (`-t 2`). Expected: all 18 pass.
2. `host-run.test.mjs` whole, once, on the fix commit.
3. The bus suite whole, three times in a row.
4. The bus suite whole in two clean worktrees at the same commit, started together.
## Slot results
Sage's CLEAR came in sage-2154 at 20:58:55Z. `slot-251.sh` ran parts A and B from 20:59:22Z to 21:01:33Z,
on node v26.8.1 and OpenSSH_10.5p1, with TMPDIR `/mnt/storage/scratch/tmp/r251`. At the start, BOOTTIME
minus MONOTONIC read 0.000001 s, then 0.000000 s twice (dewey-0769 N1). So the hrtime fractions below
put the agent's second boundary in the right place.
### Part A: T27 alone under `align-hooks.mjs`, 3 runs per fraction
The fraction is when the hook started `ssh-add`. The hook logged each start within 0.0001 of its target.
| fraction | e5e4b2e5a (`-t 1`) | 68f2dfd4b (`-t 2`) |
|---|---|---|
| 0.990 | 3 pass | 3 pass |
| 0.993 | 3 pass | 3 pass |
| 0.995 | 3 red | 3 pass |
| 0.996 | 3 red | 3 pass |
| 0.997 | 3 pass | 3 pass |
| 0.998 | 3 pass | 3 pass |
e5e4b2e5a went red 6 times out of 18, every time at a fraction the prediction named, so none of them
counted toward the stop. 68f2dfd4b passed all 18. The fix runs used 68f2dfd4b, whose test blob is the same
as c19b4167e's (280132c7).
Every red failed on the first list and got `"The agent has no identities.\n"`, with the agent alive and
its socket present. The step 1 message gave these hrtime fractions. "addKey start" is the mark before the
hook's wait, so its fraction is earlier than the ssh-add start.
| run | addKey resolved | first list mark |
|---|---|---|
| 0.995 rep 1 | 0.9992 (+317.6 ms) | 0.9993 |
| 0.995 rep 2 | 0.9991 (+846.5 ms) | 0.9991 |
| 0.995 rep 3 | 0.9988 (+845.0 ms) | 0.9988 |
| 0.996 rep 1 | 0.9999 (+839.8 ms) | 1.0000 |
| 0.996 rep 2 | 0.0004 (+845.0 ms) | 0.0004 |
| 0.996 rep 3 | 0.0002 (+835.9 ms) | 0.0002 |
At 0.995 the add finished about 4 ms in, before the boundary, and the list mark came just before it. The
`ssh-add -l` process then reached the agent after the boundary. At 0.996 the agent stored the key before
the boundary, and addKey resolved just after it. In both cases the key's whole second ended between the
add and the list, which is the cause step 2 gives. The two edge fractions came out green on the first
build. At 0.993 the list still reached the agent before the boundary, and at 0.997 the add landed after
it, so the key got almost a full second. The prediction allowed either result at those two.
### Part B: `host-run.test.mjs` whole on 68f2dfd4b
24 tests, 24 passed (21:01:18Z, 14.0 s).
### The stop before C1, and the resume
At 21:01:33Z the script stopped before C1, with exit 4 and no red. My own `busy.sh` matched two `sleep 60`
processes, mine, in `darkwing-r251/packages/bus`. Part B left them. They come from T27 "the start sweep
keeps a live bus dir and kills only a matching live process" (`host-run.test.mjs:528` and `:536`). Its
`t.after` SIGKILLs the `/bin/sh -c 'sleep 60; :'` parent, but the `sleep` child lives on for up to 60 s.
That code is outside this row's diff, so the row leaves it alone, and it's recorded below as a follow-up.
Sage confirmed (sage-2159) that the right move is to resume only the scope that hadn't run.
`slot-251-cd.sh` runs C and D as `slot-251.sh` has them, with the same two busy checks. Before each check,
it waits up to 75 s for those orphans to exit: my uid, my trees, a command line of exactly `sleep 60`.
It logs them as it waits. Any other process still trips the checks.
### Parts C and D: the bus suite whole on 68f2dfd4b
| run | start | tests | pass | fail | time |
|---|---|---|---|---|---|
| C1 | 21:02:48Z | 598 | 598 | 0 | 21.6 s |
| C2 | 21:04:06Z | 598 | 598 | 0 | 21.4 s |
| C3 | 21:05:26Z | 598 | 598 | 0 | 22.1 s |
| D1, darkwing-r251 | 21:06:44Z | 598 | 598 | 0 | 24.2 s |
| D2, darkwing-r251b, TMPDIR `r251-b` | 21:06:44Z | 598 | 598 | 0 | 24.2 s |
The script waited 33 s before C2, 34 s before C3, 33 s before D and 32 s before the sweep. Each time it
was waiting for that run's `sleep 60` orphans (two per suite, four after D), and nothing else. After the
wait, the sweep of my trees, the out dir and TMPDIR found 0 processes left (21:08:04Z). Sage confirmed
(sage-2159, sage-2160) that the short gitea-api and `node --check` runs they made during the slot weren't
stops. None of my busy checks refused on any of them.
These runs used the bus files as they were at 3b0aef995. Since then, origin/refactor (666173292, then
7247d80c7 and c3d88840c with row 196) has changed `broker.mjs`, `runtime.mjs` and `server.mjs`, which T27's
file imports, along with other bus files. None of those commits touch this row's two paths or
`host-run.mjs`, and `git merge-tree` of c3d88840c with 68f2dfd4b is clean. The gate rerun in
a clean worktree on Sage's slot runs T27 against them.
### Follow-up, not in this row
T27's start-sweep test leaves the `sleep 60` child of each planted shell running for up to 60 s after
the test. Killing the process group (`process.kill(-pid)`; both shells are spawned `detached`) or
`exec sleep 60` in the shell would end the child along with its parent.
+113
View File
@@ -0,0 +1,113 @@
# Row 273 (#1778): activate --send and --no-sso
Darkwing, 2026-10-22. Brief: `docs/plans/2026-10-22_activate-send-nosso.md` (blob in f0d0b31e9), approved by
Mos in mos-1246 (F39). Base: origin/refactor 0042f713d. Reviewer: Filbert. Gate owner: Sage.
## What changed
Only `scripts/stack-release.sh` and its two node test files.
- `activate --send` puts `--send` into the run's Console flags after `--sso` and before the tester
flags, where webui-service.sh renders it. It refuses `send-needs-sso` unless those flags hold `--sso`,
given or carried. The plan line is `send: on; --send was given`, or `send: carried` when the pointer
already holds it. `check_target` lets it name the pointer's copy only when the pointer holds no `--send`,
so a second `--send` on the same copy refuses `already-active`.
- `activate --no-sso` refuses `sso-not-on` when the pointer holds no `--sso`. Otherwise it takes the
Console flags from a rollback set (below) and keeps the pointer's tester flags. `send_for_target`
drops `--send`, whether it came from the set or the pointer. It is the only place that does, so one
mutant there (M3) shows the drop is tested. The plan prints `sso: off; Console flags from <set>/<file>; restores <flags>`
and a send line, `send: dropped; --no-sso drops --send (F39)` or `send: off; --no-sso was given and the
pointer holds no --send`. The steps log gets `drop --send; --no-sso was given` and
`turn off --sso; the Console flags come from <set>/<file>`.
- Usage errors: `--send` with `--no-send`; `--no-sso` with `--sso`, `--send`, a proof pair or
`--no-proof`; either flag on any verb but activate; either flag under `--units external`.
`--no-sso --no-send` is allowed, since the brief doesn't forbid it and both drop the same flag.
- The set read that `sso_base` did inline for a rollback to a Console without `--sso` moved into
`set_flags`, which both paths call. Its three messages are unchanged, and T57 still pins them.
- `from_log` has a new mode, `sets`: the activation of each ok activate and rollback line, newest first,
stopping at the adopt line, which took no rollback set.
## The consequential choice: which set --no-sso reads
The brief says the flags come from "the rollback set of the activation that turned `--sso` on", and that
"a set whose Console already ran with `--sso`" refuses. Each set holds the Console unit from before its
run. So the activation that turned `--sso` on is the newest one whose set holds a Console without
`--sso`: every activation after it ran with `--sso`, and so did every set it left.
`no_sso_base` walks the log's sets newest first and stops at the first set whose Console has no `--sso`.
It refuses `sso-off-flags` at the first set it can't read (missing, failing its SHA256SUMS, or with no
argv the script can parse), since it can't tell whether that set was the one. If every set ran with
`--sso`, it refuses with `the Console in <set>/<file> already ran with --sso, and the log names no
earlier activation`. A log with no set at all refuses with `the log names no activation with a rollback
set`.
The tradeoff: a later same-copy re-pin that carried `--sso` (a `--no-send`, or a re-pin onto a newer
copy) doesn't break `--no-sso`, which the phase 2 window needs if a re-pin lands between step 5 and a
rollback. The cost is that "a set that ran with `--sso` refuses" now applies only when no older set
without `--sso` exists. A tampered set on the walk still refuses, even one newer than the set the walk
would have used. Filbert, please rule on this reading.
## Filbert's early look: B1 and N1 (sage-2315)
Filbert accepted the walk. Sage ruled both findings into this row.
- B1: row 200's rollback to a copy whose Console has no `--sso` took the flags from the set and kept
the pointer's `--send`. From an `--sso --send` pointer, which this row's `--send` can now make, that
gave a Console with `--send` and no `--sso`, which F39 b forbids. `send_for_target` now drops `--send`
whenever the run drops the pointer's `--sso`, so it is still the one place that drops it. The plan
line reads `send: dropped; the run drops --sso, and --send needs it (F39)`, and the steps log gets
`drop --send; the run drops --sso`. Row 200's sso line now names only the tester as following the
pointer.
- Changed existing assertion, for the review to approve by name: T57 item 3 (sage-1389), "the
plain case", ends with `assert.deepEqual(pointer(g).console,[...noSend(gbase),...pair(f)])`. It
used to expect `'--send'` at the end. The case seeds a legacy `--send` pointer, turns `--sso` on
(which carries `--send`), then rolls back to B, whose Console has no `--sso`. A carried `--send`
that ends without `--sso` breaks F39 b whatever its source, so the old pin asserted the wrong
outcome (Sage accepted this in sage-2321). Row 200's two sso plan strings also lose "--send and"
(`ROLLBACK_PLAN` and the round 3 named-instance case), since `--send` no longer follows the pointer
on that path.
- N1: the walk skipped an ok line with no activation. Skipping it could land on an older set that
isn't the right one, so it now refuses `sso-off-flags` with `an ok line in the log names no
activation`. The walk reads the log through `mapfile`. That gives no entry for an empty log, so a
log with no set still gets its own message.
## test-release.sh
The window plan says test-release.sh "gets cases for both". The brief lists it only as a file to run
whole. test-release.sh is release.sh's Docker selftest and doesn't call stack-release.sh, so the cases
live in the node tests and test-release.sh runs unchanged.
## Tests (T58 in stack-release.test.mjs)
- T1: `--sso --send` from B onto C1 with a tester port: plan, pointer, unit and steps. `--send` beside a
carried `--sso` re-pins C1 once; a second `--send` refuses `already-active`; C carries it.
- T2: `--send` without `--sso` refuses on another copy and on the pointer's copy, also when the pointer
carries `--send` alone; `--plan` lists the refusal.
- T2 (sage-2315 B1): `--sso --send` onto C1, then a rollback to B, whose Console has no `--sso`. The
pointer and the unit end with neither flag, and the plan and steps name the drop.
- T2 and T4 usage: every forbidden pairing and verb, checked against the full usage text.
- T3: from an `--sso --send` pointer, `--no-sso` on the same copy gives back the pre-`--sso` ExecStart
line byte for byte, without `--send`. A second case walks past a `--no-send` re-pin that ran with
`--sso` and keeps the pointer's tester port (7334), which differs from the set's (7333). A third
(sage-2305 a) turns `--sso` on, then re-pins twice with `--send` and `--no-send`, so the two newest
sets both hold `--sso`. `--no-sso` then gives back the arrival's ExecStart line byte for byte.
- T4 (sage-2305 b): two sets, both edited or written to hold `--sso` with sums intact, refuse
`sso-off-flags` through `refused()`.
- T4: `sso-not-on`; an ok line with no activation; a set that fails its sums, ran with `--sso`, has no
argv or is missing; a log with no set. Each runs through `refused()`, so nothing stops and nothing else changes.
- T5: stack-release-external.test.mjs adds `--send` and `--no-sso` to the row 232 usage list.
- T55 test 5 and the T57 usage test match the new usage line.
## Mutants
| # | Change | Expected killer |
|---|---|---|
| M1 | `set_flags` skips the SHA256SUMS comparison when `--no-sso` is given | T4 tampered set |
| M2 | `no_sso_base` skips the `sso-not-on` refusal | T4 `sso-not-on` |
| M3 | the `--no-sso` branch of `send_for_target` keeps `--send` | T3 |
| M4 | `--send` skips the `send-needs-sso` refusal | T2 |
| M5 | parse_opts drops the `--send`/`--no-send` rule | T2 and T4 usage |
| M6 | a rollback to a Console without `--sso` keeps `--send` (B1) | T2 (sage-2315 B1) |
| M7 | the walk skips an ok line with no activation (N1) | T4 no-activation line |
Runs and results go in the round's notes once Sage clears the slot.
+100
View File
@@ -0,0 +1,100 @@
# Row 250 build notes (Gizmoduck)
Vikunja #450, Mos ruling mos-1182 item 3. Brief: docs/plans/2026-10-21_k12-cohort-events.md. Reviewer
Filbert, gate owner Sage. Branch gizmoduck/row250 from origin/refactor 3b0aef995. Line numbers below are on
this branch unless a commit is named.
## Step 1
cbba6ff07 changes only the K12 assertion (packages/conversation/tests/cohort.test.mjs:292 on 3b0aef995).
On failure it prints the shim's answer, with `ok` and `unavailable`, and `h.ctrl.evidence.releases`.
## Step 2: the cause
K12 reads the shim after the controller has started to release it.
- The force stop sets the binding `stopped` (packages/conversation/src/controller.mjs:1615). Then it pauses at
`scope-release` (:1623) and calls `#releaseScope` (:1624).
- `#releaseScope` calls `releaseCohort` (:1639). That sends `hello`, then `release`
(packages/conversation/src/cohort.mjs:261-265).
- The shim answers `release` and exits 10 ms later. It unlinks its socket first (shim.mjs:165-176). systemd
then collects the empty scope, `engine` included.
- K12's `forceStop` helper returns once it sees `stopped` (cohort.test.mjs:148, polling every 10 ms). The
test then reads `engine/cgroup.freeze` (:281 on 3b0aef995) and asks the shim for `events` (:291).
Nothing in K12 orders those reads before the release. Unloaded, the test usually sends its `events` request
within the 10 ms before the shim exits, so it passes. Under load the release wins. `shimRequest` then
returns `{ ok: false, unavailable: "the shim socket is gone" }` (cohort.mjs:46), or "unreachable" or
"closed the connection" (:62-63), and `ev.populated` is undefined. Both recorded failures fit this
pattern:
- Row 197 proof 4, pair1-b: K12 failed in 339 ms on `undefined !== 0` at :292. 339 ms is far below the
3 s `shimRequest` timeout (cohort.mjs:56), so the request didn't time out. The `cgroup.freeze` read at
:281 passed, so the scope still existed then.
- BUILD-LOG.md:4092, row 56's gate: "`populated` undefined with load around 12".
Row 50 (a9cc522a0, #1536) added the release after a proven stop. K12 is older. Its comment still says
"the shim holds the scope", and that stopped being true in row 50. E1 (cohort.test.mjs:923-925) already
holds `scope-release` so it can read the shim while the shim still answers. K12 never got the hold. No
other test reads the shim after a proven stop and expects an answer. R1 expects the shim gone, R3 is an
uncertain stop, and K15 reads before its stop.
The cgroup read itself has no race. The kill's `waitFor` (shim.mjs:117-126) had already read `populated 0`
for the stop to be proven. The read is the shim's call to `events()` (shim.mjs:83-96), and it reads one
kernel file. So the brief's allowance for a bounded retry doesn't apply, and there is none.
The fix goes in the test's setup, which the brief allows ("Use the shim, the cohort controller or the
test's setup and teardown, whichever the evidence points to"). The controller's release is correct and
specified (README "Scope release", R1). Delaying it for a reader would change row 50's behavior.
## Step 2: the change
packages/conversation/tests/cohort.test.mjs, K12:
- It runs with `gate()`, holds `scope-release` before the stop, and waits until the hold is reached. Then it
does every read that needs the scope: `cgroup.freeze`, `events` and `members`.
- After the reads it lets the release go and asserts one entry, `["force-stop", "released"]`. This is new
coverage: K12 now also shows the forking cohort's scope released.
- The assertions are unchanged. `populated` must be 0 and the member list must be empty. The step 1 message
stays.
packages/conversation/src/shim.mjs:167-168, `release` (:167 on 3b0aef995). It answered a failed `events()` with "the engine
cgroup is not empty", so it reported unavailable as not empty. The refusal was already safe, but the reason
was false. Now an `events()` answer that isn't `ok` is returned as it is, with the read's own reason. Only
`populated` 1 gives "not empty". Nothing else matched that string (grep of packages/).
## Tests
- K12 as above.
- R13, the brief's new case, placed after R3. After a proven stop, with `scope-release` held, the test makes
`engine/cgroup.events` mode 000. Then:
- `events` returns `ok: false`, has no `populated`, and the reason is "engine/cgroup.events unreadable
(EACCES)".
- On release, the controller records one entry `["force-stop", "unavailable", <that reason>]`.
- The unit stays active, and the shim still answers `hello`.
Then `close()` reuses the stop's release, so there is still one entry. The `finally` closes the
controller again (a second close is a no-op), restores mode 444, the kernel's mode for cgroup.events,
and releases the scope. On
3b0aef995's shim.mjs, R13 fails on the reason, because it reads "the engine cgroup is not empty".
- K15 already covers a stop, as opposed to a release, with `events` unavailable: the shim gone, the file
mode 000, or `engine` missing, each ending `uncertain` with its reason.
## File added beyond the brief
packages/conversation/README.md:599 listed "R1–R3, R5–R12" for cohort.test.mjs. With R13 it now reads
"R1–R3, R5–R13". The brief's files don't include the README. Sage added it to this row in sage-2082.
## Runs
None yet. They need Sage's slot. Sage's sage-2082 says the run is on the pushed candidate in a clean
worktree, and that it adds the control-board suite whole, so it counts as the gate run.
## Round 2
Filbert's C1 (#1755 comment 30125): R13 never closed its in-process controller, so the controller's
socket server (controller.mjs:548) stayed open, and `reap` only kills units and engines. Only `close()`
closes it (:1789). R13 now calls `h.ctrl.close()` after its checks and asserts one release entry, as R2
does. Its `finally` also closes the controller, for a failure partway through. The second close is safe:
`server` is null by then (:1791), and `#releaseScope` returns the cached release (:1634-1648). Branch
gizmoduck/row250-r2, on e9cfd92f8.
+80
View File
@@ -0,0 +1,80 @@
# Row 268 build notes (Gizmoduck)
Case 2 C2-01c, playground levels. Gitea #1773, Vikunja 464. Brief: docs/plans/2026-10-20_case2-rows.md,
"C2-01c playground levels: universe-level prefixes and draft universes" (blob a6ac627c8). The ruling is
agents/gosalyn/work/case2/DESIGN.md "Ruling after r11: playgrounds at three levels" (:49-88 at
db1e7732a), and it wins where the two differ. Reviewer Webby, gate owner Sage. Branch gizmoduck/row268
from refactor db1e7732a. Line numbers are on this branch unless a commit is named.
## mapping.mjs
- `kind` (:71). An entry with a `kind` key must say `"hybrid"` or `"draft"`. Before, a plain universe
with `kind` refused as an unknown key, and `kind: "draft"` did the same.
- A draft (:73-86) refuses `books` with its own message before the key check, so the cause reads
plainly. It takes `kind`, `name` and `playground`, needs one prefix or more, and comes back as
`{ kind: 'draft', name, books: [], playground }`, frozen with the rest.
- The prefix level check moved out of the per-entry loop to :127-140, after every entry is read,
because it needs every entry's books. It runs after the book-holds-book, prefix-holds-prefix and
listed-twice checks, so an overlap still reports as before. For each prefix and each book folder:
- equal to any book folder: `library-mapping-invalid` (brief, What ships, "or equal to a book folder");
- inside or holding another entry's book: `library-playground-other-universe`, and the message says
which, with the book and its entry;
- holding its own entry's book: `library-mapping-invalid`.
A prefix inside its own book is book level, and one that touches no book is universe level. A draft
has no books, so its prefixes are either outside every book or refused as another entry's.
- `sources` (:117-120) now refuses any entry with a `kind`, so a draft can't be a source either. The
message changed from "not a non-hybrid universe" to "not a plain universe". mapping.test.mjs:61 on
db1e7732a pinned the old words, and that line moves with it.
- `library-playground-outside-book` is gone from packages and docs/case2.
## scope.mjs
`classify` (:12-24) now runs two passes. The first finds a prefix in any entry, and the file is
`playground` with that entry's id and `book` set to the entry's book folder that holds the prefix, or
`null`. The second finds a book folder, and the file is `canon`. `place` (:26-32) works out `type` and
`folder` from the first folder under the prefix or the book, as before. The validator keeps prefixes
from overlapping, so the first pass finds at most one.
## Readers on this base
- reader.mjs:182 and :194 spread `classify`'s answer into each file record, so a universe-level or
draft file carries `book: null`. Nothing in the reader reads `book`.
- reader.mjs:206-209. R5 fires for a canon file linking into any playground. R6 fires when the target's
universe differs and isn't a source. A draft can't be a source, so a link into one gives R5 and R6.
A link into the file's own universe-level prefix gives R5 only. No code change was needed. The new
test covers both.
- survey.mjs:66 and mirror.mjs:246 read only `scope`. mirror.mjs:251 calls `readLibrary` and checks
only `path`.
- No other package calls this `classify` or reads `universes` (git grep over packages/*/src).
- C2-05a (row 216) and C2-06 (row 217) have no candidate branch on origin at the time of writing. I
check again before in-review.
## Tests
- mapping.test.mjs: the old outside-book test splits into the other-universe refusals (inside another
entry's book, holding one, a draft prefix inside a book), the mapping-invalid ones (equal to a book,
own or another's, and holding its own), universe level in a plain universe and in a hybrid with
`books: []`, and the draft shape (frozen with `books: []`, with `books`, with none or empty
prefixes, an extra key, and five bad `kind` values). The overlap test gains a cross-entry hold and a
cross-entry duplicate. The hybrid test gains a draft source. The caller-supplied `fail` test now
uses `Second World/Playground/` and sees the new code, since `Elsewhere/` is valid now.
- scope.test.mjs, new: one case per level (book, universe in a plain universe, universe in a hybrid,
draft), `.canvas` under each new prefix staying unmapped, prefixes first inside a book that holds a
prefix, and the reader case for R5 and R6, with `book: null` on the read records.
- business.test.mjs: `Elsewhere/Playground/` now loads, and `Second World/Playground/` refuses with
`library-playground-other-universe` through `validateBusinessDocument`.
## Docs
- packages/library/README.md: the universe keys, a line on the three levels, the refusal table and
the Scope section.
- docs/case2/SETUP-SHEET.md "Your playground": one line per level and the draft entry.
- docs/case2/SURVEY-RUN.md:114: the new code and message.
## Hash pins
`git grep -E '[0-9a-f]{64}'` over packages/*/tests finds no pin on any changed file.
## Runs
None yet. Every run waits for Sage's CLEAR.
File diff suppressed because it is too large Load Diff
+399
View File
@@ -0,0 +1,399 @@
# Row 215 build record
Owner: Herb. Reviewer: Filbert. Claim: herb-215-start, rev 2753.
Brief: docs/plans/2026-10-20_case2-rows.md, C2-04, pin
16847e94ae1768881711318fe81cbb5e6c66b971. Design: Gosalyn r11,
sections 1.1, 1.3, 2.1, M4, M9 and 7.2.
Base: a2c72ac952df94ecab7e391d5042f49b7f88b816, row 212's reviewed
candidate, under Q177. Branch: herb/row215. Rebuild on the combined 212+197
landing when Sage names it. Do not replay or revert landed changes.
## Scope rulings (sage-1848)
1. Add packages/bus/src/runtime.mjs for library adapter injection and the
library skip in protectedRoots. The skip has its own consumer test. Change
nothing else there.
2. Add packages/library/src/mirror.mjs, named mirror rather than store. It
creates a bare mirror without a remote, reads at the served commit and
reads sync state. Export one state filename constant, and export this
module through packages/library/src/index.mjs. Row 222 imports the constant.
3. Add packages/bus/src/server.mjs only to await library.file and
library.status, with socket tests proving their replies are results, not {}.
4. HTTP exposure is deferred. Row 225 owns the library.status route;
C2-11b owns library pages. Leave serve.mjs alone. webui/src/cli.mjs gets
only callback wiring next to the project-file callbacks on both listeners.
5. Reuse existing refusal codes where possible.
6. Build on a2c72ac95 until Sage supplies the 212 landing SHA.
Additional ruling, sage-1852: library-state-invalid is approved for malformed,
unsafe or mismatched state, including a missing file while the served ref
exists. The version-1 shared contract below is approved. Row 222 also owns
runtime.mjs for its sync and timer wiring, recorded in that row's manifest.
Additional rulings, sage-1860, after Filbert's CHANGES on 6a508804f:
- B1 adds packages/cli/src/config.mjs and cli/tests/trackers-boot.test.mjs.
trackerFor skips library entries. A real bus boot with tracker.baseUrl and
a library entry succeeds and gets no tracker from that entry.
- B2 refuses a role publish grant naming a library project in the loader.
- B3 readers use the sync lock. mirror.mjs exports withLibraryLock, with a
5000 ms wait bound and library-sync-busy on timeout. Sync locks only the
served-ref move and state publication, never the fetch.
- N3, git-socket doPush/recordPush accepting a library ID, is a later row.
Sage records it in DEFERRED.md when 215 lands. No fix here.
## Shared contract with row 222
Agreed in herb-0048, megavolt-0081/0082 and sage-1852. This section is the
contract Filbert reviews and Megavolt cites for Dewey. It is not a sync writer.
Module: packages/library/src/mirror.mjs. Export
`SYNC_STATE_FILE = 'mosaic-library-sync.json'`. The full path is
`join(mirror, SYNC_STATE_FILE)`, inside the bare repository. Only sync.mjs
writes it, using a sibling temp file, fsync, rename, and directory fsync.
Version 1 has exactly these keys:
```json
{
"version": 1,
"branch": "main",
"served": null,
"lastFetch": null,
"lastError": null,
"diverged": null,
"problems": []
}
```
- branch is the configured mapping branch and must match the reader's branch.
- served is null or a full lowercase 40-hex commit ID. refs/heads/served is
authority. The state value must equal that ref, which must name a commit.
- lastFetch is null or a canonical ISO UTC timestamp.
- lastError is null or exactly {code, at}. code matches [a-z-]{1,64}; at is
canonical ISO UTC. It never holds stderr, prose or a credential value.
- diverged is null or exactly {commit, at}, with the same commit/time rules.
- problems uses C2-01's exact {code, path, message, line?}. code is R1-R7,
message is that code's fixed RULES text, line is a positive safe integer
when present. No file body or arbitrary message is accepted.
- An absent file and absent served ref return the empty object above with
the configured branch. An absent file with an existing served ref refuses.
- Unknown keys, bad types, unsafe filesystem metadata, invalid JSON and a
mismatch refuse library-state-invalid. Reads use a bounded regular owned
private file without following links. The read never repairs state.
- refs/remotes/library/<branch> is not duplicated in this file. Row 222 may
read it when it needs the fetched tip.
Exports consumed by row 222:
| Export | Contract |
| --- | --- |
| mirrorPath({business, project, dataRoot}) | Require a library entry; return projectRepo's checked path |
| ensureMirror(context) | Promise of the mirror path; race-safe idempotent bare init; no remote or fetch |
| servedCommit(mirror) | Promise of full commit ID or null; refs/heads/served only |
| readSyncState(mirror, {branch}) | Promise of checked version-1 state; compare with served ref |
| withLibraryLock(mirror, operation) | Await the same exclusive lock as readers, then await operation({readSyncState}); always release |
The lock is a stable private regular file, mosaic-library-sync.lock inside
the mirror. It is never unlinked. A kernel flock is held by the caller's
file descriptor. Acquisition waits at most 5000 ms and refuses
library-sync-busy on timeout. Unsafe lock metadata refuses library-state-invalid.
There is no fs.flock API. The exact protocol is: open the stable file with
O_NOFOLLOW, check regular/private/owned/single-link metadata, pass its fd as
child FD 3 to `flock --exclusive --wait 5 --conflict-exit-code 75 3` using
asynchronous spawn, await
that child's exit 0, then run the callback while the parent still holds its
original descriptor. Both descriptors share the open file description, so
the lock survives acquisition-child exit. Finally close the parent's fd.
Process death also closes it. No holder child stays alive in the callback,
no stale file is deleted, and unrelated operation children inherit only 0-2.
Acquisition never uses spawnSync: the bus must serve other calls while a
library reader waits (megavolt-0087).
A scratch-only probe on node v26.8.1 at 2026-10-21T08:34:00Z confirmed this:
acquisition child exited 0; a second flock -n returned 1 while the parent fd
stayed open; after closeSync it returned 0. The probe removed its own root.
This was a design probe, not a package test, suite, Docker command or proof.
readSyncState takes that lock around reading the served ref and state file.
The callback-scoped readSyncState({branch}) supplied by withLibraryLock reads
under the already-held lock and does not reacquire it. The helper awaits
asynchronous callbacks and releases in finally. A callback-scoped reader
used after its callback ends refuses; it cannot escape its lock lifetime.
Row 222 fetches without this lock, then calls withLibraryLock across the
served-ref move and atomic state write. Its tests hold publication between
those steps and prove a concurrent reader waits, then sees the new consistent
pair, including first sync. Row 215 tests that publication shape and timeout.
A file read releases the lock after snapshotting a consistent served SHA,
then reads that immutable commit. Row 222's separate outer sync lock spans
fetch to prevent two sync operations interleaving (megavolt-0086).
Host adapter exports for this row:
- libraryStatus(context): return {project, ...state}, with no repository path
or content. Do not fetch or rebuild on this read.
- readLibraryFile({...context, path}): read a mapped, readable file at the
served commit, returning {project, path, commit, content}. No caller-picked
commit, working-tree fallback, fetch or write. C2-01's R1/R2/R7 exclusions
apply. A never-synced mirror refuses library-commit for a file read.
Row 222 owns syncCommand, sync, writeSyncState, its outer sync lock, the timer
and state publication. Row 215 owns the shared publication/read lock per
sage-1860. Sync rebuilds problems with existing readLibrary({repo, commit,
mapping}). It does not need a second rebuild function from this row.
## Root consumer audit and reviewer notes
On a2c72ac95, ran exactly the sage-1860 audit:
`git grep -n 'entry\.root\|\.root)' -- 'packages/*/src/*.mjs'`.
It has 14 hits. runtime.mjs:95, business/cli.mjs:61,72,78,118,
instance-settings.mjs:142-143, resolve.mjs:44, cli/config.mjs:62-63 and
project/paths.mjs:42 all get the library guard in this row. business.mjs:233
and :242 are code-project validation after the library dispatch, not an
unguarded consumer. queue.mjs:1144 compares queue genesis roots and never
iterates business.projects; it is safe and unchanged. A second search for
projectFilePath/business.projects/b.projects found no additional root reader.
ensureCheckouts destructures root rather than spelling .root; its guard and
the launcher's filter remain in T1. Bare projectFilePath/loadProject remain
strict and unchanged.
Filbert N1 is handled at the existing owned paths.mjs boundary: projectRepo
defaults to record-project use and refuses a library entry with project-invalid.
Only mirrorPath's internal call opts into allowLibrary. Thus ordinary human
and tester project init cannot occupy an uninitialized library mirror path.
No record API or bus argument exposes that internal option.
Filbert N2: ensureMirror failure refuses bus boot. No socket becomes ready.
The runtime closes resources it opened, following its existing failure path.
A T4 test uses an unsafe/pre-existing mirror and proves the refusal, rather
than serving a half-configured library adapter.
## Verification record
Planning stage. No product code, package test, suite, image build, live operation
or push for row 215 has happened. Product implementation awaits Filbert's
manifest approval. Sage owns suite and full-gate grants. Row 197's proof
commit is separate and is not part of this row's changes.
## Implementation ledger
Plan: agents/herb/work/queue-215/MANIFEST.md at 2522025af, approved by
Filbert in the reply to herb-0056. Execution is inline by Herb under the
executing-plans and test-driven-development skills. No agent delegation.
Pre-flight: T1's validated mapping feeds T2's protected document and T3's
mirror context; T3's lock/state helpers feed T4's injected host adapter.
The shared contract is pinned above, with no interface conflict found.
Sage-1867 overrides COMMON's singles allowance: every test and executable
probe needs an explicit slot grant. No further probe/test has run since
that reminder. Read-only inspection and test authoring continue while the
slot is held. The first red execution is pending; product code is unchanged.
Filbert's approved-manifest build notes: ensureMirror creates the lock in
its staged directory before publication; a missing lock in an existing
mirror refuses library-state-invalid. The top-level metadata allowlist
permits only normal bare Git entries plus the state and stable lock files.
Tests cover an unknown entry. The host needs /usr/bin/flock; ENOENT yields
a handled library-state-invalid refusal, never an unhandled child error.
Lock acquisition uses /usr/bin/flock directly and asynchronous spawn.
T1: consumer and loader regression tests authored, not run. T2: direct M9
write and serialization regression tests authored, not run. No completion
or passing claim is made for either task.
Update, sage-1869 focused slot, 2026-10-21T08:47:48Z to 08:51:07Z:
the busy check was CLEAR before the first test. T1's mapping regression
failed 0/1 on unsupported kind as expected, then passed 2/0 with the publish
refusal included. Other focused greens: business CLI 1/0, resolver 1/0,
instance settings 1/0, seat checkout 1/0, project paths 1/0, sandboxed launcher
1/0, and trackers-boot whole 2/0. T2's direct writer case failed 0/1 on the
missing refusal, then its whole new file passed 4/0. Green total 14/0 with
no skips or cancellations. Logs and counts: granted-t1/. The final /proc
scan found no test process or fixture cwd/argv reference; its private root
was removed. Clear sent as herb-0062. Existing files' other cases have not
been run yet, so T1/T2's full-file acceptance is still pending.
Sage-1874 directs continued authoring and implementation while the landing
gate/proof holds the slot. T3/T4 tests were written first. Their initial
red runs cannot execute during that hold; implementation proceeds under
that owner direction, with no claim of a witnessed red or green for T3/T4.
Their baseline red and candidate checks need a later explicit grant.
Implementation ruling: initialization uses GNU /usr/bin/mv --no-clobber
--no-target-directory for its bounded staging rename, rather than Node's
renameSync, which could replace an empty directory made by a competing
actor. This preserves pre-existing directories and needs no new package on
the Linux/coreutils bus host. The host also needs /usr/bin/flock. Neither
command has been executed for T3/T4 yet. Publication errors fail closed.
Sync-state reads are capped at 8 MiB, including a checked extra byte; an
oversized or concurrently changed file refuses library-state-invalid.
T5 static dependency audit found two partial pinned-copy fixtures that copy
business/src but not its newly imported library mapping validator:
cli/tests/stack-release.test.mjs:1772 and stack-release-fixture.mjs:309 on
the base. Each now includes packages/library/src in the existing copy list.
This changes fixture closure only, no assertion or timeout. The complete
stack-release file and each test file importing that shared fixture require
a later granted single-file run. They are outside the four-file T3/T4 slot.
Sage-1879 places the four-file T3/T4 focused run after their 212+197 proof,
before Gizmoduck, and requires their explicit clear before starting. No run
has occurred after herb-0062. Row 197's N7 base-image-tag finding is left to
Sage's DEFERRED entry; no row 197 source change is made here.
Focused T3/T4 grant sage-1882, 2026-10-21 09:13-09:18Z: busy check
CLEAR. Final whole-file results: mirror 16/0, bus library 6/0, WebUI
library-cli 1/0, project-runtime 3/0, all with zero skips/cancellations.
Syntax checks passed for the four files and five changed host modules.
Logs, all intermediate failures, counts and empty /proc cleanup scan are
in granted-t34/. Baseline a2c72ac95: three focused bus cases 0/3
(unknown-verb), CLI 0/1 (missing callbacks). Mirror baseline 0/1 is an
import refusal for the new module, not a witnessed behavioral red.
An initial whole baseline bus attempt left a runtime after its expected
failure; its exact runner was stopped, no process remained, and the
baseline was narrowed to the three bounded broker/socket cases.
Candidate fixture setup errors and a mock-child timeout were corrected
before the final whole-file passes. No suite or Docker/image/proof ran.
Filbert's 09:09 publication note: no dependence on mv -n's skipped-rename
exit status across coreutils versions. Save staging dev/ino before mv;
only its disappearance plus the same dev/ino at the mirror proves a win.
A remaining original staging inode means a loss, requiring a valid winner.
Only that owned original staging inode is removed. Numeric no-clobber
statuses 0 and 1, including a diagnostic, are tested with a competing
winner. Spawn errors, signals/timeouts and inconsistent outcomes refuse.
Coreutils must provide atomic --no-clobber --no-target-directory semantics;
it need not use any particular skipped-rename status convention.
Landing update: prepared herb/row215-on212 on fefd54e13 (includes row 212
landing ee7b2f137), without history replay or stash. Only BUILD-LOG.md and
cli/tests/stack-release.test.mjs overlap the a2c72ac95-to-landing change
set. BUILD-LOG preserves all landed entries and appends the existing Herb
entry. stack-release preserves the landing plus only the library/src copy
list addition. No test has run on this landing yet. All further execution
requires a Sage grant (supersedes the original COMMON singles rule).
Sage-1979 early singles on fefd54e13, stopped after file10 per sage-1983:
- packages/bus/tests/library.test.mjs: 6/0
- packages/bus/tests/project-runtime.test.mjs: 3/0
- packages/business/tests/business.test.mjs: 18/0
- packages/business/tests/cli.test.mjs: 10/0
- packages/business/tests/instance-settings.test.mjs: 12/0
- packages/business/tests/library-projects-protected.test.mjs: 4/0
- packages/business/tests/resolve.test.mjs: 11/0
- packages/cli/tests/launcher.test.mjs: 30/0
- packages/cli/tests/stack-release-external.test.mjs: 25/0
- packages/cli/tests/stack-release.test.mjs: 196/0
Total315/0, zero skips/cancellations. This is an early read only, not gate
evidence for a merged candidate. The sequential driver was paused during
file10 so it could not start file11, then terminated after file10 exited0.
No file11 log exists. Results/logs are in granted-singles/. Later landed
overlaps require a new branch and all16 singles again after their merge.
Current merge preparation per sage-1983/1987: new branch
herb/row215-on-f827 on f8276c5eb3ecb0aad353ee65e3dd8f7faac5dd87.
Nine landed overlaps are recorded in current-MERGE.json and the exact
candidate deltas against the landing are in current-merge-only.patch.
Two textual conflicts were resolved manually: Broker keeps hostKeys and
adds library; business.mjs keeps CREATIVE_DEFINITIONS and adds checkMapping.
The other seven merges preserve the landing plus the original C2-04 edits.
All other product/test paths match the early215 tree byte for byte. The
landing's BUILD-LOG is preserved and the existing Herb entry is appended.
No history replay, stash, force push, branch deletion or source commit.
No test/probe/syntax check ran on the merged tree after the slot was cleared.
All16 whole singles need a new Sage grant before candidate commit/review.
The10 old-base315/0 results are early reads only, never merged gate evidence.
## Source candidate and sage-2008 receipts
Source commit: e333af4dc7af3a9d3789c8c0db26a0df8bdddfee, on
f8276c5eb3ecb0aad353ee65e3dd8f7faac5dd87. SOURCE.json records all30
changed source/phase-log paths with blob SHA256 and Git mode. The next
commit contains only this packet. No product change follows the tests.
Sage-2008: start13:15:15.548943Z; end13:27:27.279728Z, 2026-10-21.
Each of the16 TEST-FILES.txt files ran whole, sequentially. All451 passed,
with0 failed/skipped/cancelled. Per-file outputs are in granted-2008/;
EVIDENCE.json records their counts, Node durations and log hashes.
| File | Pass/fail |
| --- | ---: |
| packages/bus/tests/library.test.mjs | 6/0 |
| packages/bus/tests/project-runtime.test.mjs | 3/0 |
| packages/business/tests/business.test.mjs | 19/0 |
| packages/business/tests/cli.test.mjs | 10/0 |
| packages/business/tests/instance-settings.test.mjs | 13/0 |
| packages/business/tests/library-projects-protected.test.mjs | 4/0 |
| packages/business/tests/resolve.test.mjs | 11/0 |
| packages/cli/tests/launcher.test.mjs | 31/0 |
| packages/cli/tests/stack-release-external.test.mjs | 25/0 |
| packages/cli/tests/stack-release.test.mjs | 196/0 |
| packages/cli/tests/stack-upgrade.test.mjs | 21/0 |
| packages/cli/tests/trackers-boot.test.mjs | 2/0 |
| packages/library/tests/mirror.test.mjs | 16/0 |
| packages/project/tests/project.test.mjs | 15/0 |
| packages/seat/tests/checkout.test.mjs | 78/0 |
| packages/webui/tests/library-cli.test.mjs | 1/0 |
All29 product/test hashes equal their pre-run values. The final /proc cwd
and argv scan was empty; the private test root was removed. Clear sent
as herb-0082 at13:28:38Z. No further runs occurred. No image, Docker,
lane, proof or full suite was part of this grant.
Before commit, checked f8276c5eb..8a04f4aa4 on every215 source path and
all16 test paths: no overlap. Row248's grant-executor test is outside them.
The current root-consumer grep still returns14 sites, all classified by
the earlier audit: business code roots after dispatch, guarded library
skips at consumers, and queue's unrelated genesis-root invariant.
Sage-2016 authorizes the source commit, packet receipts and seat-only push,
then Filbert's candidate review. The landing gate stays with Sage. Earlier
focused and old-base receipts are archived in granted-t1/, granted-t34/
and granted-singles/, with their limits kept above. Full package/shell
landing checks and implementation approval remain pending. No HTTP route,
remote fetch, sync writer, timer or live operation was added.
Raw Node error logs retain their original space-only lines. Packet whitespace
checks exclude *.log so the recorded output bytes stay unchanged; source and
other packet files pass the ordinary diff check.
## Merge-scoped round 2 after rows 249 and 197 (sage-2054)
Row197 landed as a36523713 after proof6 passed 8/8 gates. Row249's
broker fix is also in this base. Source a6101e21c596e2972ab5a7e3087736075899505e, single parent a3652371338e60187d4e95cee4a0f30f3c8a8f06,
is on the new herb/row215-on197 branch. The old branch stays untouched.
No force push, branch deletion or stash was used. The merge was clean.
Only broker.mjs and stack-release.test.mjs changed upstream among the30
source/phase-log paths. Both retain every landed hunk. The broker keeps
row249's installed flag and hostLineLeft handling. The stack-release test
keeps row197's leasePort import, after hook and closed-port lease helper;
215 adds only its library fixture copy. The other28 blobs and all modes
match the prior source. All30 paths have exactly the same -U0 added and
removed lines as f8276c5eb..e333af4dc. BUILD-LOG.md is append-only.
Stable patch-id, old and new: d191e7e9586cd3b7c18310daacdf0c1fdbc8be01.
r2-range-diff.txt reports only the commit message changed (!); its patch
is unchanged.
r2-MERGE.json records every path; r2-merge-only.patch contains both merged
source diffs against the new base. SOURCE.json names the new source.
Filbert approved round1 in comment30071, rev2897. This round requests
merge-only review. The earlier451/0 receipts belong to the prior base;
no test, probe, syntax check, Docker command or lane ran in this round.
Merged-tree evidence remains pending Sage's explicit clear.
r2-TEST-FILES.txt proposes all16 original whole singles plus all66
bus test files whose local import closure reaches broker.mjs, deduplicated
to80 whole files. This includes shared fixture and runtime imports.
Sage's slot and row196 landing gate come first. The full landing gate
remains Sage's. No library behavior, assertion or timeout changed.
+261
View File
@@ -0,0 +1,261 @@
{
"grant": "sage-2008",
"sourceCommit": "e333af4dc7af3a9d3789c8c0db26a0df8bdddfee",
"base": "f8276c5eb3ecb0aad353ee65e3dd8f7faac5dd87",
"start": "2026-10-21T13:15:15.548778+00:00",
"end": "2026-10-21T13:27:27.279728+00:00",
"summary": {
"tests": 451,
"pass": 451,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"results": [
{
"file": "packages/bus/tests/library.test.mjs",
"rc": 0,
"counts": {
"tests": 6,
"pass": 6,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "01-packages_bus_tests_library.test.mjs.log",
"sha256": "c19016583abc936534f41bde26eb39e095496eda944622142b1d18ffd5018bc0",
"nodeDurationMs": 359.869493
},
{
"file": "packages/bus/tests/project-runtime.test.mjs",
"rc": 0,
"counts": {
"tests": 3,
"pass": 3,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "02-packages_bus_tests_project-runtime.test.mjs.log",
"sha256": "f123231042d1562fee6dad3d885ac587967e0287f63603c861d04c1907363530",
"nodeDurationMs": 312.717335
},
{
"file": "packages/business/tests/business.test.mjs",
"rc": 0,
"counts": {
"tests": 19,
"pass": 19,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "03-packages_business_tests_business.test.mjs.log",
"sha256": "ae990ebccf7f21b9b32fd9c25fdd637873251c078e17dd95471b82b548322980",
"nodeDurationMs": 199.91256
},
{
"file": "packages/business/tests/cli.test.mjs",
"rc": 0,
"counts": {
"tests": 10,
"pass": 10,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "04-packages_business_tests_cli.test.mjs.log",
"sha256": "28512dc38e2bce04294048a2f1c7dc7cf0c322028f874d1d35aa83b87570ea54",
"nodeDurationMs": 2381.258516
},
{
"file": "packages/business/tests/instance-settings.test.mjs",
"rc": 0,
"counts": {
"tests": 13,
"pass": 13,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "05-packages_business_tests_instance-settings.test.mjs.log",
"sha256": "146623624012da1399fb4ee3de4c0e13df1af28caae81f4870315a67ac368f8f",
"nodeDurationMs": 608.08011
},
{
"file": "packages/business/tests/library-projects-protected.test.mjs",
"rc": 0,
"counts": {
"tests": 4,
"pass": 4,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "06-packages_business_tests_library-projects-protected.test.mjs.log",
"sha256": "675ae18523218ab70a4addee039f41df6ae08ee414f20527f975a2004dde65b2",
"nodeDurationMs": 118.54831
},
{
"file": "packages/business/tests/resolve.test.mjs",
"rc": 0,
"counts": {
"tests": 11,
"pass": 11,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "07-packages_business_tests_resolve.test.mjs.log",
"sha256": "6557938cb8e6d79617c0ea911a6e0ad7303cf5a4f9dd5a5631c70d093e6b06fe",
"nodeDurationMs": 94.022587
},
{
"file": "packages/cli/tests/launcher.test.mjs",
"rc": 0,
"counts": {
"tests": 31,
"pass": 31,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "08-packages_cli_tests_launcher.test.mjs.log",
"sha256": "c3404f1931595914d469cb00a951a5e1648f7792733c86bddbfa099552e5791c",
"nodeDurationMs": 24989.261794
},
{
"file": "packages/cli/tests/stack-release-external.test.mjs",
"rc": 0,
"counts": {
"tests": 25,
"pass": 25,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "09-packages_cli_tests_stack-release-external.test.mjs.log",
"sha256": "55224b6204d4d05a4cb669fb2845aa4168ab868223c3f341ab026032c47458b4",
"nodeDurationMs": 96332.465778
},
{
"file": "packages/cli/tests/stack-release.test.mjs",
"rc": 0,
"counts": {
"tests": 196,
"pass": 196,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "10-packages_cli_tests_stack-release.test.mjs.log",
"sha256": "3f7430ce1f28482a5b66fc0e7db76969298f0dd32c3793e242bc10110dc44e6e",
"nodeDurationMs": 533493.817869
},
{
"file": "packages/cli/tests/stack-upgrade.test.mjs",
"rc": 0,
"counts": {
"tests": 21,
"pass": 21,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "11-packages_cli_tests_stack-upgrade.test.mjs.log",
"sha256": "d18a3eff64b39a5128ab7e842721748b214dc3037ecc9d2c93c58f023859d695",
"nodeDurationMs": 60456.446459
},
{
"file": "packages/cli/tests/trackers-boot.test.mjs",
"rc": 0,
"counts": {
"tests": 2,
"pass": 2,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "12-packages_cli_tests_trackers-boot.test.mjs.log",
"sha256": "22e56455a6a56c98b02f942ed3d38504e4f8486343235c4035c88b967625bce5",
"nodeDurationMs": 502.723687
},
{
"file": "packages/library/tests/mirror.test.mjs",
"rc": 0,
"counts": {
"tests": 16,
"pass": 16,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "13-packages_library_tests_mirror.test.mjs.log",
"sha256": "def30105a3d561118e360535d9d3129226361f2e7902f316e47c74f65f1c0e47",
"nodeDurationMs": 5963.543966
},
{
"file": "packages/project/tests/project.test.mjs",
"rc": 0,
"counts": {
"tests": 15,
"pass": 15,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "14-packages_project_tests_project.test.mjs.log",
"sha256": "e63ab0d23ab24aa1f1f2c1ea8017e8541f415dcb20dff697a37bb7a6368e174d",
"nodeDurationMs": 355.86586
},
{
"file": "packages/seat/tests/checkout.test.mjs",
"rc": 0,
"counts": {
"tests": 78,
"pass": 78,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "15-packages_seat_tests_checkout.test.mjs.log",
"sha256": "c9e7168f589047b8176ec4ee8d2f03adb9fb68f692b363ba6d0036f670811ede",
"nodeDurationMs": 4880.907448
},
{
"file": "packages/webui/tests/library-cli.test.mjs",
"rc": 0,
"counts": {
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "16-packages_webui_tests_library-cli.test.mjs.log",
"sha256": "1ca5b92bc4f2de9ef6aa7156cdea62742fb9ddf054ebfb56a9548431e0cfec0d",
"nodeDurationMs": 191.536271
}
],
"cleanup": {
"processes": [],
"removed": "/mnt/storage/scratch/gt-j5nm6iqn",
"sourceHashesUnchanged": 29
},
"drift": {
"base": "f8276c5eb",
"tip": "8a04f4aa4aba493eb89e92aa9e0c34084be7058e",
"overlap": []
},
"earlierRuns": {
"granted-t1": "focused T1/T2 red/green on a2c72ac95",
"granted-t34": "focused T3/T4 with baseline import refusal and corrected fixture errors recorded",
"granted-singles": "old fefd54e13 early315/0, not merged gate evidence"
},
"mergeRound": {
"sourceCommit": "a6101e21c596e2972ab5a7e3087736075899505e",
"base": "a3652371338e60187d4e95cee4a0f30f3c8a8f06",
"status": "not-run; awaiting Sage clear",
"priorReceipts": "sage-2008 receipts apply to e333af4dc on f8276c5eb only"
}
}
+246
View File
@@ -0,0 +1,246 @@
# C2-04 library business entry implementation plan
> Execution: Herb implements in this session using the executing-plans skill.
> Filbert approves this manifest before any product implementation.
**Goal:** Load a library mapping without giving an agent a checkout, protect
all project mappings from organization writes, and expose human-only bus
reads of the bare mirror's served commit and sync status.
**Architecture:** C2-01 checks mappings and reads commits. Business consumers
skip library entries. A host-only mirror module owns local mirror reads and
initialization. runtime.mjs injects that module into the broker, preserving
the broker's sandbox import closure. Sync and HTTP routes belong to later rows.
**Tech stack:** Existing Node ESM, node:test and bounded local Git helpers.
No dependencies. node_modules remains the plain canonical symlink.
**Spec:** Pinned C2-04 brief; Gosalyn's approved design 1.1, 1.3, 2.1,
M4/M9 and 7.2; BUILD.md's sage-1848/1852/1860 scope and shared-state contract.
## Constraints and review focus
- Base a2c72ac95; rebase only onto Sage's named 212 landing on a new branch.
- Existing code-project entries retain root/repo/remote behavior. Library
entries have no root or repo override and use C2-01 checkMapping.
- Preserve the broker's pinned imports. No library import in broker.mjs and
no new runner mount. Do not edit serve.mjs or add an HTTP route.
- No fetch, remote config, credential read, timer, sync writer, bus write or
live operation. All tests use made-up content and local repositories.
- All test/probe runs require Sage's grant (sage-1867), including singles.
Stop and report two failures in one gate. Do not push without authorization.
Review focus, with owning tasks below: library-first iteration must not
break a later code project (T1); serialized documents cannot bypass M9
(T2); racing initialization must not replace another mirror (T3); stale or
unsafe state must not leak content (T3); async socket replies must preserve
the result and human-only admission (T4).
## Paths and evidence from the base
| Product path | Allowed change |
| --- | --- |
| packages/business/src/business.mjs | Dispatch kind:library to checkMapping before code-root validation (:228-244) |
| packages/business/src/organization.mjs | Compare canonical projects in write before publication (:177 onward) |
| packages/business/src/cli.mjs | Skip library roots in validate, resolve --project and credential exclusion roots (:60-61, :71-79, :118) |
| packages/business/src/resolve.mjs | Skip library entries while matching a project file (:44) |
| packages/business/src/instance-settings.mjs | Skip library before projectFilePath (:142), nothing else |
| packages/seat/src/checkout.mjs | Skip library entries in ensureCheckouts (:315-327) |
| packages/cli/src/launcher.mjs | Pass only code entries to ensureCheckouts (:354) |
| packages/cli/src/config.mjs | Skip library entries in trackerFor's root loop (:61-63), per sage-1860 |
| packages/project/src/paths.mjs | Skip library entries in overlap loop (:41-44); default record use refuses library, mirrorPath internally opts into allowLibrary |
| packages/library/src/mirror.mjs | New bare init, checked state and served-commit file read module |
| packages/library/src/index.mjs | Export mirror module interface and SYNC_STATE_FILE |
| packages/bus/src/broker.mjs | New injected library adapter, human-only admission and two async verbs |
| packages/bus/src/runtime.mjs | Library adapter wiring, mirror initialization and protectedRoots skip only |
| packages/bus/src/server.mjs | Await the two library verbs only |
| packages/webui/src/cli.mjs | Two library read callbacks beside project callbacks on main and tester listener; no routes |
packages/business/src/project.mjs need not change: callers skip a library
before projectFilePath or loadProject. Do not weaken their code-root checks.
scripts/stack-copy.sh already copies packages/library/src and needs no edit.
Test paths to modify: business/tests/business.test.mjs, cli.test.mjs,
resolve.test.mjs, instance-settings.test.mjs; seat/tests/checkout.test.mjs;
cli/tests/launcher.test.mjs, trackers-boot.test.mjs; project/tests/project.test.mjs;
bus/tests/project-runtime.test.mjs. All paths are under packages/.
T5 dependency-audit addition: cli/tests/stack-release.test.mjs and its
stack-release-fixture.mjs helper copy packages/library/src alongside
business/src, whose validator now imports the mapping checker. No assertions
or timeouts change. Whole-file dependent singles require a later Sage slot.
New tests: packages/business/tests/library-projects-protected.test.mjs;
packages/library/tests/mirror.test.mjs; packages/bus/tests/library.test.mjs;
packages/webui/tests/library-cli.test.mjs. Reuse existing made-up fixtures.
If a new fixture is needed, define it within the corresponding new test file.
No unrelated test assertion or timeout changes.
Record paths: agents/herb/work/queue-215/{MANIFEST.md,BUILD.md}, later source
manifest and test receipts there; one append-only BUILD-LOG.md phase for this
row when implementation begins. Sage owns docs/SESSIONS.md.
## T1. Mapping and consumer skips
- [ ] Add failing business tests: accept a frozen library mapping without a
root, retain mixed/only-library documents, and propagate C2-01's mapping
and playground refusal codes. A role publish naming a library project
refuses at load, even with an otherwise valid fork identity and remote.
Existing code validation remains unchanged.
- [ ] Add a separate regression for each consumer: CLI validate and its
credential exclusion roots; CLI resolve --project books has no project
layer; resolveInstance finds a later real project; instance settings still
writes an unrelated setting; ensureCheckouts returns no library checkout;
launcher creates no books workspace clone; projectRepo skips a library
while still rejecting overlap with a real code root. Put books first.
- [ ] Add the real bus boot regression in cli/tests/trackers-boot.test.mjs:
tracker.baseUrl is set and books is a library entry; boot succeeds, books
produces no tracker, and a later real tracker project still works.
- [ ] projectRepo(context, {allowLibrary = false} = {}) refuses a library
entry by default with project-invalid. Only mirrorPath opts in. Test that
ordinary human and tester record initialization leave no directory at the
future mirror path; no public verb can supply allowLibrary.
- [ ] Add runtime's protectedRoots consumer regression in T4's real runtime
fixture. A valid credential outside code/data roots still loads with a
library entry, and existing credential protections still refuse unsafe files.
- [ ] Run each changed test singly, confirm the new cases fail for the
intended root assumption, then implement only the table's guards and
checkMapping dispatch. Re-run the same files singly.
CLI validate reports a library entry as library, without an absent-project
warning. CLI resolve --project books resolves without loading a project file,
as the design's skip requires. It does not fabricate project vars or a root.
## T2. M9 at the organization writer
- [ ] In the new business test file, call organizationWriter.write directly
with a changed playground prefix and, separately, a changed hybrid entry.
Both refuse organization-projects-protected. Snapshot business bytes and
changes.jsonl before each call; assert both remain identical afterward.
- [ ] Cover addition, deletion and changes to code projects too, since M9
protects all projects. Reordered object keys are allowed; array changes
are changes. An ordinary non-project write still succeeds and logs.
- [ ] Add a serialization regression so a proposed object's serialized
projects cannot differ from those checked before publication.
- [ ] Run the file singly to see the missing refusal, compare canonical
projects against old disk JSON and the document that actually lands,
before any publication/receipt, then re-run it singly.
## T3. Bare mirror, served file and read-only state
- [ ] Add failing tests for mirrorPath and ensureMirror: exact projectRepo
default, no checkout/remote, retained no-links/overlap protections,
idempotence, simultaneous creators and preservation of a pre-existing
non-mirror directory or unsafe Git metadata.
- [ ] Add failing state tests for BUILD.md's full version-1 contract: missing
state/ref, valid state, malformed JSON, unknown keys/types, branch mismatch,
invalid times/IDs/problem shape, symlink/hardlink/non-private file and
missing or mismatched served ref. Assert reads change no bytes.
- [ ] Add shared-lock tests: withLibraryLock(mirror, operation) waits at most
5000 ms, refuses library-sync-busy on timeout and releases after a throw.
A reader arriving between the publisher's served move and state write
waits, then reads the consistent pair. Cover first sync and fast-forward
in independent processes, same-process independent acquisitions and
release after owner death. A callback-scoped reader expires on release.
- [ ] Implement the lock by async spawn of flock on the parent's inherited
fd 3, as pinned in BUILD.md. Await acquisition-child exit before calling
operation. Never spawnSync during acquisition, which could freeze the bus
for 5 s. A parallel ordinary bus read remains responsive while a library
read waits. Only the publication/read lock is shared; fetch is outside it.
- [ ] Add served-read tests against local fixtures containing spaces,
capitals, a .canvas and unmapped top-level note. Seed a served ref and state
without fetching. A newer unrelated HEAD does not change the returned
file. R1/R2/R7 exclusions, traversal, a missing path, a caller commit and a
never-synced mirror refuse. Status contains no fixture body marker.
- [ ] Implement mirror.mjs and index exports using the shared contract.
Initialization stages into an owned private sibling directory and renames
atomically; a losing creator validates the winner and removes only its own
staging directory. Git uses existing bounded safe helpers, no remote.
- [ ] Run mirror.test.mjs singly, red then green. Re-run C2-01 reader.test.mjs
singly if a reader assumption changes. Do not change C2-01 reader rules.
mirror.mjs validates a bare mirror's metadata before Git reads it, including
no remote/include/indirection. It does not apply inspectGit's working-tree
.git assumption to a bare repository. Failures reuse library-git or the
approved library-state-invalid code. State reads are bounded and private.
## T4. Human-only bus reads and deferred Console callbacks
- [ ] Add broker tests for library.file and library.status using injected
adapters. Each agent session refuses human-required before adapter access,
including bad arguments and an absent adapter. A reader or tester does not
gain a human capability. Human calls validate keys, then use checked
library context. Secret checking and bounded refusal evidence remain.
- [ ] Add real startBroker/socket tests for both verbs. Seed only local
fixture mirror data. Assert result shape/content and an actual status
field, not merely successful transport or {}. Cover never-synced status
and runtime's protectedRoots skip. Keep the pinned import-closure test.
- [ ] Inject a separate library adapter via runtime, initialize only library
mirrors, add the two async request paths, and include them in server's
awaited list. Do not import library or fs into broker.
- [ ] Unsafe or pre-existing non-mirror initialization refuses bus boot,
closes opened resources and never publishes a ready socket. Add that
runtime regression in library.test.mjs.
- [ ] Wire main and tester Console callbacks beside project-file callbacks.
No HTTP route is shipped. The new CLI test captures the startServer
options with a test-only module loader and calls the supplied callbacks
against a fixture bus, checking the verb and transport identity. No
product test seam, UI page, endpoint or serve.mjs change is needed.
- [ ] Run library.test.mjs, project-runtime.test.mjs and library-cli.test.mjs
individually, red then green. Human admission may use the existing
fixture authentication seam; do not forge or bypass a live identity.
## T5. Review packet and Sage's gate
- [ ] Search packages/*/tests and scripts/test-*.sh for each changed source
path, imports and 64-hex pins. Record the resulting test-file list and run
each changed/touched test singly. Stop and report a scope error.
- [ ] Self-review the diff against this manifest and all acceptance cases.
Record results, commands, skips, source hashes and remaining limits in
BUILD.md. Request Sage's slot for the required business, seat, cli, project,
bus and webui suites and every scripts/test-*.sh. Do not run them ungranted.
- [ ] After applicable tests are green, make the source commit, build the
candidate manifest from the authorized pushed tip, and move the row with
scripts/mosaic. Sage posts token-requiring requests; Filbert reviews the
candidate. Push only when authorized. No merge or live change by Herb.
## Self-review
Every brief acceptance item is in T1-T4. Added host support paths match
sage-1848/1860. BUILD.md records the complete 14-hit root audit. State shape
and authority match sage-1852; the shared read/publication lock follows
sage-1860 and Megavolt's agreement. Fetch, timers, outer sync serialization
and HTTP stay with their later rows.
This is a plan only; no product edits or test execution precede approval.
Landing update: prepared herb/row215-on212 on fefd54e13 (includes row 212
landing ee7b2f137), without history replay or stash. Only BUILD-LOG.md and
cli/tests/stack-release.test.mjs overlap the a2c72ac95-to-landing change
set. BUILD-LOG preserves all landed entries and appends the existing Herb
entry. stack-release preserves the landing plus only the library/src copy
list addition. No test has run on this landing yet. All further execution
requires a Sage grant (supersedes the original COMMON singles rule).
Current merge preparation per sage-1983/1987: new branch
herb/row215-on-f827 on f8276c5eb3ecb0aad353ee65e3dd8f7faac5dd87.
Nine landed overlaps are recorded in current-MERGE.json and the exact
candidate deltas against the landing are in current-merge-only.patch.
Two textual conflicts were resolved manually: Broker keeps hostKeys and
adds library; business.mjs keeps CREATIVE_DEFINITIONS and adds checkMapping.
The other seven merges preserve the landing plus the original C2-04 edits.
All other product/test paths match the early215 tree byte for byte. The
landing's BUILD-LOG is preserved and the existing Herb entry is appended.
No history replay, stash, force push, branch deletion or source commit.
No test/probe/syntax check ran on the merged tree after the slot was cleared.
All16 whole singles need a new Sage grant before candidate commit/review.
The10 old-base315/0 results are early reads only, never merged gate evidence.
Candidate status: source commit e333af4dc7af3a9d3789c8c0db26a0df8bdddfee. The16 whole
singles passed451/0 under sage-2008 with zero skips/cancellations; raw
outputs and merge receipts are in this packet, indexed by EVIDENCE.json
and SOURCE.json. Sage keeps the landing gate. Filbert's implementation
review and that gate remain pending. Prior315/0 on fefd54e13 is early
read evidence only. Sage-2016 authorizes the seat-only source/packet push.
+157
View File
@@ -0,0 +1,157 @@
{
"sourceCommit": "a6101e21c596e2972ab5a7e3087736075899505e",
"base": "a3652371338e60187d4e95cee4a0f30f3c8a8f06",
"paths": [
{
"path": "BUILD-LOG.md",
"sha256": "b7c8f906f2db095dfe02d29b4064836af83ae877a87b38c09466b8c9a327e019",
"mode": "100644"
},
{
"path": "packages/bus/src/broker.mjs",
"sha256": "762234259d72db87aff510751fe14003099759ae37bc8eaf14f1ea1de61d2005",
"mode": "100644"
},
{
"path": "packages/bus/src/runtime.mjs",
"sha256": "c759e665514a5bcad40536bc9f979837187fe6523462b3c0a0687182885c3ac6",
"mode": "100644"
},
{
"path": "packages/bus/src/server.mjs",
"sha256": "207c7a76752c753ef7380f18f9ce1a4f23e8fa35142973b6358aaf0c45f44899",
"mode": "100644"
},
{
"path": "packages/bus/tests/library.test.mjs",
"sha256": "58d839709e005571b398a11abda5a50f542bab0e37bd97b72f43cde926434301",
"mode": "100644"
},
{
"path": "packages/business/src/business.mjs",
"sha256": "5fd3fee664d3e70153d988b4c1a42406f888a31b3927fa52375e66619d8cec59",
"mode": "100644"
},
{
"path": "packages/business/src/cli.mjs",
"sha256": "31cfe9090900169416e1207dc6db865b046439273917e9de442add4159c89eb3",
"mode": "100644"
},
{
"path": "packages/business/src/instance-settings.mjs",
"sha256": "3873411688d927336a909d0299acddeba92a653102c660d276471f5a0f6eebd3",
"mode": "100644"
},
{
"path": "packages/business/src/organization.mjs",
"sha256": "eaa650499d2d078552a2c0e5b13a6126e0aca02f73160032a7325b3f5aa90964",
"mode": "100644"
},
{
"path": "packages/business/src/resolve.mjs",
"sha256": "712183465fee099606223d84423afc54e3386a0c8e468c68d5ac39c1e676d109",
"mode": "100644"
},
{
"path": "packages/business/tests/business.test.mjs",
"sha256": "910f924d90b6f9b18c1f36d97cebd0b7a0a15a080b73530c4fb847bfd435fb3f",
"mode": "100644"
},
{
"path": "packages/business/tests/cli.test.mjs",
"sha256": "d54ba2338bcdfec22764030cf3ec9e3464ab275b15321d6d1b039169ea37fd85",
"mode": "100644"
},
{
"path": "packages/business/tests/instance-settings.test.mjs",
"sha256": "072e25c5237e1be4294c7a4a95daa2ab593832786013e1283b19725c330647c1",
"mode": "100644"
},
{
"path": "packages/business/tests/library-projects-protected.test.mjs",
"sha256": "cfc726ec7bf73c1dc9fc6395cba85e374b7a006bac45bd8c47c969550557147f",
"mode": "100644"
},
{
"path": "packages/business/tests/resolve.test.mjs",
"sha256": "c74af44a83ee918196e2c1016b62786e757c0ae774c9b1613280d3fbbf3ffb22",
"mode": "100644"
},
{
"path": "packages/cli/src/config.mjs",
"sha256": "ab7b9f452123b6f7e98f4f59cbb3cc4483fd296e4af5522412410ee53d1b20e0",
"mode": "100644"
},
{
"path": "packages/cli/src/launcher.mjs",
"sha256": "d8a870d8eb8254adfd576688696705359fefbf517b40e5d7482500bf27941022",
"mode": "100644"
},
{
"path": "packages/cli/tests/launcher.test.mjs",
"sha256": "99234f247e8414120d2fd214efd590e9d573eca86180d7b775cb506ac1c12c78",
"mode": "100644"
},
{
"path": "packages/cli/tests/stack-release-fixture.mjs",
"sha256": "86a3c15446d4c119206097c754cd15f5bef7b7e93c9f16e9d8696ec8c475eea9",
"mode": "100644"
},
{
"path": "packages/cli/tests/stack-release.test.mjs",
"sha256": "46cc95a6e75a89b907cdd5e9bd03853e1d1f75f1ced81084d968c5476466822e",
"mode": "100644"
},
{
"path": "packages/cli/tests/trackers-boot.test.mjs",
"sha256": "c90493e45bfa09bf0b01600aaf04d6ecd79d939357beacc1a9cd65f958bb7c22",
"mode": "100644"
},
{
"path": "packages/library/src/index.mjs",
"sha256": "ecda99dea46390b81fffa6f414d56a13737233e01d883b82cd186e0e21b6c4d1",
"mode": "100644"
},
{
"path": "packages/library/src/mirror.mjs",
"sha256": "75ce08fba0956c846f2ca9ab1b565974657925f94ac952c720e7308783946480",
"mode": "100644"
},
{
"path": "packages/library/tests/mirror.test.mjs",
"sha256": "e96473be70ece8c8232ce5325822244b593f273371b74b4126920654cde9d72c",
"mode": "100644"
},
{
"path": "packages/project/src/paths.mjs",
"sha256": "bd9debcf3f9bb92560e74f220a0b108dfe24154bc6d1fb03c5ec414356d451f9",
"mode": "100644"
},
{
"path": "packages/project/tests/project.test.mjs",
"sha256": "0ebe1862ce70f26c4a6a44304182327f9a6944d16cc87f674fbb1d32e08ac602",
"mode": "100644"
},
{
"path": "packages/seat/src/checkout.mjs",
"sha256": "859201bd192522e51c33b86ef45f73487b27cdfc48ea7e4e09870e26d056048a",
"mode": "100644"
},
{
"path": "packages/seat/tests/checkout.test.mjs",
"sha256": "663040d7c737aba6403e6397bacf1325b558cc14df3e5677f0a28f9499c45127",
"mode": "100644"
},
{
"path": "packages/webui/src/cli.mjs",
"sha256": "e98fd2fe4e621d5b4fa1ed95c4d4585469e7ab7a11dd379dc541dddb1b1a1f02",
"mode": "100644"
},
{
"path": "packages/webui/tests/library-cli.test.mjs",
"sha256": "1983912b76f01f8e6d0c564e3bbcd763c0b2c44044988b192ef827446f98556c",
"mode": "100644"
}
],
"priorSourceCommit": "e333af4dc7af3a9d3789c8c0db26a0df8bdddfee"
}
+16
View File
@@ -0,0 +1,16 @@
packages/bus/tests/library.test.mjs
packages/bus/tests/project-runtime.test.mjs
packages/business/tests/business.test.mjs
packages/business/tests/cli.test.mjs
packages/business/tests/instance-settings.test.mjs
packages/business/tests/library-projects-protected.test.mjs
packages/business/tests/resolve.test.mjs
packages/cli/tests/launcher.test.mjs
packages/cli/tests/stack-release-external.test.mjs
packages/cli/tests/stack-release.test.mjs
packages/cli/tests/stack-upgrade.test.mjs
packages/cli/tests/trackers-boot.test.mjs
packages/library/tests/mirror.test.mjs
packages/project/tests/project.test.mjs
packages/seat/tests/checkout.test.mjs
packages/webui/tests/library-cli.test.mjs
@@ -0,0 +1,229 @@
{
"base": "fefd54e13",
"landing": "f8276c5eb3ecb0aad353ee65e3dd8f7faac5dd87",
"overlaps": [
"packages/bus/src/broker.mjs",
"packages/bus/src/runtime.mjs",
"packages/business/src/business.mjs",
"packages/business/src/instance-settings.mjs",
"packages/business/tests/business.test.mjs",
"packages/business/tests/instance-settings.test.mjs",
"packages/cli/src/launcher.mjs",
"packages/cli/tests/launcher.test.mjs",
"packages/library/src/index.mjs"
],
"initialConflicts": [
"packages/bus/src/broker.mjs",
"packages/business/src/business.mjs"
],
"remainingConflicts": [],
"resolutions": {
"packages/bus/src/broker.mjs": "retain landed hostKeys constructor parameter and add library parameter",
"packages/business/src/business.mjs": "retain landed CREATIVE_DEFINITIONS import and add checkMapping import"
},
"sourcePaths": [
{
"path": "packages/bus/src/broker.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "dca11dd58561bf3f399d18d7521aefa93f43e0ee3de9c532c1da073ce5a17466",
"sha256": "925b3dd6744565349ec421a58b3374ce3d21181e6c2d3d32ffb99c79271b9450"
},
{
"path": "packages/bus/src/runtime.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "6f69e9fb838cf1d8ee60d1b7090395193d3dbfdc6492a401a4b72c356c371ca9",
"sha256": "c759e665514a5bcad40536bc9f979837187fe6523462b3c0a0687182885c3ac6"
},
{
"path": "packages/bus/src/server.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "207c7a76752c753ef7380f18f9ce1a4f23e8fa35142973b6358aaf0c45f44899",
"sha256": "207c7a76752c753ef7380f18f9ce1a4f23e8fa35142973b6358aaf0c45f44899"
},
{
"path": "packages/bus/tests/library.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "58d839709e005571b398a11abda5a50f542bab0e37bd97b72f43cde926434301",
"sha256": "58d839709e005571b398a11abda5a50f542bab0e37bd97b72f43cde926434301"
},
{
"path": "packages/business/src/business.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "e132f92211cd35f951e85ff5e90471a2316755cb35ad7eabf516e780ce6c13ad",
"sha256": "5fd3fee664d3e70153d988b4c1a42406f888a31b3927fa52375e66619d8cec59"
},
{
"path": "packages/business/src/cli.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "31cfe9090900169416e1207dc6db865b046439273917e9de442add4159c89eb3",
"sha256": "31cfe9090900169416e1207dc6db865b046439273917e9de442add4159c89eb3"
},
{
"path": "packages/business/src/instance-settings.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "04d61c1ec89a5d67da75aede46c3a8e2197aadd63b11bef8e4fc37d90e8862cf",
"sha256": "3873411688d927336a909d0299acddeba92a653102c660d276471f5a0f6eebd3"
},
{
"path": "packages/business/src/organization.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "eaa650499d2d078552a2c0e5b13a6126e0aca02f73160032a7325b3f5aa90964",
"sha256": "eaa650499d2d078552a2c0e5b13a6126e0aca02f73160032a7325b3f5aa90964"
},
{
"path": "packages/business/src/resolve.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "712183465fee099606223d84423afc54e3386a0c8e468c68d5ac39c1e676d109",
"sha256": "712183465fee099606223d84423afc54e3386a0c8e468c68d5ac39c1e676d109"
},
{
"path": "packages/business/tests/business.test.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "55aafd3f0eea816d5f42283b7b74323dd32a698d7b95dd9cc54529dbf7b16058",
"sha256": "910f924d90b6f9b18c1f36d97cebd0b7a0a15a080b73530c4fb847bfd435fb3f"
},
{
"path": "packages/business/tests/cli.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "d54ba2338bcdfec22764030cf3ec9e3464ab275b15321d6d1b039169ea37fd85",
"sha256": "d54ba2338bcdfec22764030cf3ec9e3464ab275b15321d6d1b039169ea37fd85"
},
{
"path": "packages/business/tests/instance-settings.test.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "c0959f3af3df653bbf469471d3bbe3f28bc4478cba8ba57910af7f09498c4c3f",
"sha256": "072e25c5237e1be4294c7a4a95daa2ab593832786013e1283b19725c330647c1"
},
{
"path": "packages/business/tests/library-projects-protected.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "cfc726ec7bf73c1dc9fc6395cba85e374b7a006bac45bd8c47c969550557147f",
"sha256": "cfc726ec7bf73c1dc9fc6395cba85e374b7a006bac45bd8c47c969550557147f"
},
{
"path": "packages/business/tests/resolve.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "c74af44a83ee918196e2c1016b62786e757c0ae774c9b1613280d3fbbf3ffb22",
"sha256": "c74af44a83ee918196e2c1016b62786e757c0ae774c9b1613280d3fbbf3ffb22"
},
{
"path": "packages/cli/src/config.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "ab7b9f452123b6f7e98f4f59cbb3cc4483fd296e4af5522412410ee53d1b20e0",
"sha256": "ab7b9f452123b6f7e98f4f59cbb3cc4483fd296e4af5522412410ee53d1b20e0"
},
{
"path": "packages/cli/src/launcher.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "2272232dbe6c362eddb23ad903987fd4b8e0ca1eb94dbe93b66b3e785e7d0712",
"sha256": "d8a870d8eb8254adfd576688696705359fefbf517b40e5d7482500bf27941022"
},
{
"path": "packages/cli/tests/launcher.test.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "38fb51c15764333642d75db7fb358d611e448049cf894b24686ea6cf2cf169c4",
"sha256": "99234f247e8414120d2fd214efd590e9d573eca86180d7b775cb506ac1c12c78"
},
{
"path": "packages/cli/tests/stack-release-fixture.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "86a3c15446d4c119206097c754cd15f5bef7b7e93c9f16e9d8696ec8c475eea9",
"sha256": "86a3c15446d4c119206097c754cd15f5bef7b7e93c9f16e9d8696ec8c475eea9"
},
{
"path": "packages/cli/tests/stack-release.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "aa63fadca8b3a51e7af1ff7560fd2645f3ef56bf4e5625b2d5a5f6946db40996",
"sha256": "aa63fadca8b3a51e7af1ff7560fd2645f3ef56bf4e5625b2d5a5f6946db40996"
},
{
"path": "packages/cli/tests/trackers-boot.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "c90493e45bfa09bf0b01600aaf04d6ecd79d939357beacc1a9cd65f958bb7c22",
"sha256": "c90493e45bfa09bf0b01600aaf04d6ecd79d939357beacc1a9cd65f958bb7c22"
},
{
"path": "packages/library/src/index.mjs",
"merged": true,
"sameAsEarlyTree": false,
"earlySha256": "b80e6c27daf908a0e71fcc1e5e8c91d05e857790bd77d92e06ff086ab3623306",
"sha256": "ecda99dea46390b81fffa6f414d56a13737233e01d883b82cd186e0e21b6c4d1"
},
{
"path": "packages/library/src/mirror.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "75ce08fba0956c846f2ca9ab1b565974657925f94ac952c720e7308783946480",
"sha256": "75ce08fba0956c846f2ca9ab1b565974657925f94ac952c720e7308783946480"
},
{
"path": "packages/library/tests/mirror.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "e96473be70ece8c8232ce5325822244b593f273371b74b4126920654cde9d72c",
"sha256": "e96473be70ece8c8232ce5325822244b593f273371b74b4126920654cde9d72c"
},
{
"path": "packages/project/src/paths.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "bd9debcf3f9bb92560e74f220a0b108dfe24154bc6d1fb03c5ec414356d451f9",
"sha256": "bd9debcf3f9bb92560e74f220a0b108dfe24154bc6d1fb03c5ec414356d451f9"
},
{
"path": "packages/project/tests/project.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "0ebe1862ce70f26c4a6a44304182327f9a6944d16cc87f674fbb1d32e08ac602",
"sha256": "0ebe1862ce70f26c4a6a44304182327f9a6944d16cc87f674fbb1d32e08ac602"
},
{
"path": "packages/seat/src/checkout.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "859201bd192522e51c33b86ef45f73487b27cdfc48ea7e4e09870e26d056048a",
"sha256": "859201bd192522e51c33b86ef45f73487b27cdfc48ea7e4e09870e26d056048a"
},
{
"path": "packages/seat/tests/checkout.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "663040d7c737aba6403e6397bacf1325b558cc14df3e5677f0a28f9499c45127",
"sha256": "663040d7c737aba6403e6397bacf1325b558cc14df3e5677f0a28f9499c45127"
},
{
"path": "packages/webui/src/cli.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "e98fd2fe4e621d5b4fa1ed95c4d4585469e7ab7a11dd379dc541dddb1b1a1f02",
"sha256": "e98fd2fe4e621d5b4fa1ed95c4d4585469e7ab7a11dd379dc541dddb1b1a1f02"
},
{
"path": "packages/webui/tests/library-cli.test.mjs",
"merged": false,
"sameAsEarlyTree": true,
"earlySha256": "1983912b76f01f8e6d0c564e3bbcd763c0b2c44044988b192ef827446f98556c",
"sha256": "1983912b76f01f8e6d0c564e3bbcd763c0b2c44044988b192ef827446f98556c"
}
]
}
@@ -0,0 +1,155 @@
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
index 3baa709f9..512d57519 100644
--- a/packages/bus/src/broker.mjs
+++ b/packages/bus/src/broker.mjs
@@ -90,0 +91 @@ export const PROJECT_FILE_VERBS = Object.freeze(['project.files', 'project.file'
+export const LIBRARY_VERBS = Object.freeze(['library.file', 'library.status']);
@@ -281,0 +283 @@ export class Broker {
+ #library;
@@ -297 +299 @@ export class Broker {
- constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null }) {
+ constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, library = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null }) {
@@ -308,0 +311 @@ export class Broker {
+ this.#library = library;
@@ -2189,0 +2193,2 @@ export class Broker {
+ if (LIBRARY_VERBS.includes(request.verb))
+ return this.#requestLibrary(s, { verb: request.verb, args: structuredClone(args) });
@@ -2438,0 +2444,20 @@ export class Broker {
+ // C2-04 M4: only a verified human reads library text or status. Host Git
+ // functions stay injected, preserving this module's sandbox import closure.
+ async #requestLibrary(s, request) {
+ const { verb, args } = request;
+ try {
+ this.#human(s);
+ const fields = verb === 'library.file' ? ['project', 'path'] : ['project'];
+ keys(args, fields, fields);
+ if (!this.#library?.readFile || !this.#library?.status) fail('project-not-configured');
+ const context = { business: this.#business(s.business), project: args.project };
+ const result = verb === 'library.file'
+ ? await this.#library.readFile({ ...context, path: args.path })
+ : await this.#library.status(context);
+ this.#secretCheck(result);
+ return result;
+ } catch (error) {
+ const Failure = this.#library?.LibraryError;
+ throw this.#refused(s, Failure && error instanceof Failure ? new BusError(error.code) : error, request);
+ }
+ }
diff --git a/packages/bus/src/runtime.mjs b/packages/bus/src/runtime.mjs
index f81f956f2..e265ff5d1 100644
--- a/packages/bus/src/runtime.mjs
+++ b/packages/bus/src/runtime.mjs
@@ -5,0 +6 @@ import * as projectModule from '../../project/src/index.mjs';
+import * as libraryModule from '../../library/src/index.mjs';
@@ -95 +96 @@ export async function startBroker({
- ...Object.values(businesses).flatMap((b) => Object.values(b.projects ?? {}).map((p) => p.root)),
+ ...Object.values(businesses).flatMap((b) => Object.values(b.projects ?? {}).filter(p => p.kind !== 'library').map((p) => p.root)),
@@ -130,0 +132,5 @@ export async function startBroker({
+ // C2-04: mirrors are host state, never session checkouts. An unsafe
+ // existing mirror refuses startup before a socket can become ready.
+ for (const business of Object.values(businesses))
+ for (const [project, entry] of Object.entries(business.projects ?? {}))
+ if (entry.kind === 'library') await libraryModule.ensureMirror({ business, project, dataRoot });
@@ -138,0 +145,5 @@ export async function startBroker({
+ library: {
+ readFile: context => libraryModule.readLibraryFile({ ...context, dataRoot }),
+ status: context => libraryModule.libraryStatus({ ...context, dataRoot }),
+ LibraryError: libraryModule.LibraryError,
+ },
diff --git a/packages/business/src/business.mjs b/packages/business/src/business.mjs
index d08a4f52e..e8337ea2b 100644
--- a/packages/business/src/business.mjs
+++ b/packages/business/src/business.mjs
@@ -12,0 +13 @@ import { checkPlugins } from "../../plugins/src/kinds.mjs";
+import { checkMapping } from "../../library/src/mapping.mjs";
@@ -241,0 +243,4 @@ export function validateBusinessDocument(document, file, { rolesDir }) {
+ if (entry.kind === 'library') {
+ projects[id] = checkMapping(entry, `${file} projects.${id}`, { fail: (code, message) => refuse(`${code}: ${message}`) });
+ continue;
+ }
@@ -257,0 +263 @@ export function validateBusinessDocument(document, file, { rolesDir }) {
+ if (projects[project].kind === 'library') refuse(`${file} roles.${instance}.publish names library project ${project}; library repositories cannot be published by agents`);
diff --git a/packages/business/src/instance-settings.mjs b/packages/business/src/instance-settings.mjs
index ff3a6d9d3..50cc36edc 100644
--- a/packages/business/src/instance-settings.mjs
+++ b/packages/business/src/instance-settings.mjs
@@ -149,0 +150 @@ export function instanceSettings({ dir, rolesDir, system, levels = () => null })
+ if (entry.kind === 'library') continue;
diff --git a/packages/business/tests/business.test.mjs b/packages/business/tests/business.test.mjs
index b64659fa1..0f214a005 100644
--- a/packages/business/tests/business.test.mjs
+++ b/packages/business/tests/business.test.mjs
@@ -10,0 +11,30 @@ import { launchDoc, businessDoc, REPO, REPO_ROLES, rolesCopy, tmp, writeJson } f
+test('C2-04: library mappings load without roots, remain frozen, and retain mapping refusals', async t => {
+ const { mappingEntry } = await import('../../library/tests/helpers.mjs');
+ const { rmSync } = await import('node:fs');
+ const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
+ const doc = businessDoc(root), file = join(root, 'businesses/acme.json');
+ doc.projects = { books: mappingEntry(), ...doc.projects };
+ const loaded = validateBusinessDocument(doc, file, { rolesDir: REPO_ROLES });
+ const { checkMapping } = await import('../../library/src/mapping.mjs');
+ assert.deepEqual(loaded.projects.books, checkMapping(mappingEntry()));
+ assert.equal(loaded.projects.books.root, undefined);
+ assert.ok(Object.isFrozen(loaded.projects.books.universes.harbor.books));
+ const only = structuredClone(doc); delete only.projects.stack;
+ assert.deepEqual(Object.keys(validateBusinessDocument(only, file, { rolesDir: REPO_ROLES }).projects), ['books']);
+ doc.projects.books.universes.harbor.playground = ['Elsewhere/Playground/'];
+ refuses(() => validateBusinessDocument(doc, file, { rolesDir: REPO_ROLES }), /library-playground-outside-book/);
+ doc.projects.books = { ...mappingEntry(), root: '/forbidden' };
+ refuses(() => validateBusinessDocument(doc, file, { rolesDir: REPO_ROLES }), /library-mapping-invalid/);
+});
+
+test('C2-04: an otherwise valid publish grant cannot name a library repo', async t => {
+ const { mappingEntry } = await import('../../library/tests/helpers.mjs');
+ const { rmSync } = await import('node:fs');
+ const root = tmp(); t.after(() => rmSync(root, { recursive: true, force: true }));
+ const doc = businessDoc(root);
+ doc.projects.books = { ...mappingEntry(), remote: 'https://git.example/writer/books' };
+ doc.roles.coder.identity = { name: 'Coder', email: '[email protected]', gitea: { url: 'https://git.example', user: 'acme-coder' } };
+ doc.roles.coder.publish = ['books'];
+ refuses(() => validateBusinessDocument(doc, join(root, 'businesses/acme.json'), { rolesDir: REPO_ROLES }), /publish.*library/);
+});
+
diff --git a/packages/business/tests/instance-settings.test.mjs b/packages/business/tests/instance-settings.test.mjs
index 6b19f9e89..a6c8fe665 100644
--- a/packages/business/tests/instance-settings.test.mjs
+++ b/packages/business/tests/instance-settings.test.mjs
@@ -22,0 +23,10 @@ const LEVELS = { pi: ["off", "minimal", "low", "medium", "high", "xhigh", "max"]
+test('C2-04: instance settings skip a library while checking a later code project', async () => {
+ const { mappingEntry } = await import('../../library/tests/helpers.mjs');
+ const f = fixture(doc => { doc.projects = { books: mappingEntry(), ...doc.projects }; });
+ writeJson(projectFilePath(f.doc.projects.stack.root), { projectVersion: 1, id: 'stack', vars: { 'tracker.project': 3 } });
+ const before = f.settings.read('acme');
+ f.settings.set('acme', { revision: before.revision, instance: 'gate-215', op: 'add', definition: 'merge-gate' }, 'jason');
+ assert.deepEqual(JSON.parse(readFileSync(f.file, 'utf8')).projects.books, mappingEntry());
+ assert.ok(f.settings.read('acme').instances['gate-215']);
+});
+
diff --git a/packages/cli/src/launcher.mjs b/packages/cli/src/launcher.mjs
index 604cef203..c721ee9e8 100644
--- a/packages/cli/src/launcher.mjs
+++ b/packages/cli/src/launcher.mjs
@@ -354 +354 @@ export function createLauncher({
- checkouts = await ensureCheckouts({ workspace, projects: b.projects, env });
+ checkouts = await ensureCheckouts({ workspace, projects: Object.fromEntries(Object.entries(b.projects).filter(([, entry]) => entry.kind !== 'library')), env });
diff --git a/packages/cli/tests/launcher.test.mjs b/packages/cli/tests/launcher.test.mjs
index 3b663ba94..4636df3de 100644
--- a/packages/cli/tests/launcher.test.mjs
+++ b/packages/cli/tests/launcher.test.mjs
@@ -32,0 +33,10 @@ const skip = sandboxProblem() ?? false;
+test('C2-04: launcher workspace clones code but never the library', { skip, timeout: 30000 }, async t => {
+ const { mappingEntry } = await import('../../library/tests/helpers.mjs');
+ const f = await setup(t, doc => { doc.projects = { books: mappingEntry(), ...doc.projects }; return doc; });
+ await f.launcher.launchPm();
+ const workspace = workspaceDir(f.f.dataRoot, 'acme', 'chief');
+ assert.equal(existsSync(join(workspace, 'stack', '.git')), true);
+ assert.equal(existsSync(join(workspace, 'books')), false);
+ assert.equal(await f.close(), 0);
+});
+
diff --git a/packages/library/src/index.mjs b/packages/library/src/index.mjs
index f64d77860..53ce5cb43 100644
--- a/packages/library/src/index.mjs
+++ b/packages/library/src/index.mjs
@@ -8,0 +9 @@ export { survey, formatSurvey, surveyMain } from './survey.mjs';
+export { SYNC_STATE_FILE, mirrorPath, ensureMirror, withLibraryLock, servedCommit, readSyncState, libraryStatus, readLibraryFile } from './mirror.mjs';
@@ -0,0 +1,14 @@
✔ C2-04 M4: both verbs reject every agent before arguments or adapter access (30.144386ms)
✔ C2-04 bus: human reads validate keys and return adapter results without path leakage (11.434614ms)
✔ C2-04 socket: each async library verb resolves its result rather than serializing {} (17.684117ms)
✔ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (45.253455ms)
✔ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (23.246812ms)
✔ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (107.052077ms)
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 359.869493
@@ -0,0 +1,11 @@
✔ Broker keeps the sandbox client import closure: only the listed imports, no lazy project import (2.265255ms)
✔ without a trusted project adapter all three async paths refuse project-not-configured (57.448982ms)
✔ real startBroker injects the project adapter for socket reads and bound-author record writes (102.784823ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 312.717335
@@ -0,0 +1,27 @@
✔ C2-04: library mappings load without roots, remain frozen, and retain mapping refusals (12.852402ms)
✔ C2-04: an otherwise valid publish grant cannot name a library repo (2.693014ms)
✔ config directory and file path follow MOSAIC_CONFIG (0.229245ms)
✔ the fixture business validates and comes back frozen (1.006969ms)
✔ two instances may share a definition (0.94271ms)
✔ top-level refusals (3.474817ms)
✔ arbiters and projects (5.543282ms)
✔ role instances (2.7386ms)
✔ Vikunja bots (6.181044ms)
✔ a role without Vikunja takes no tracker block (5.900981ms)
✔ credential references match the definition's services (3.012597ms)
✔ launch (8.561546ms)
✔ REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses (6.420869ms)
✔ loadBusiness: file checks (2.241353ms)
✔ loadBusiness: not a regular file (46.573969ms)
✔ loading writes nothing (2.802338ms)
✔ names that are Object.prototype properties don't count as declared (3.291238ms)
✔ the shipped example refuses as written and validates once filled in (1.195924ms)
✔ persona vars: agent layer, creative definition only, both or neither (9.0736ms)
ℹ tests 19
ℹ suites 0
ℹ pass 19
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 199.91256
@@ -0,0 +1,18 @@
✔ C2-04: business validate and resolve skip a library before code projects and credential checks (235.663956ms)
✔ usage errors exit 4 (325.805811ms)
✔ validate: a good business exits 0 and prints instance digests (69.331646ms)
✔ validate: project files (344.919464ms)
✔ validate: missing files and a broken system config (252.779029ms)
✔ validate: credential reference problems exit 2 and name each one (66.663076ms)
✔ validate: a token file inside the repository is refused (78.714732ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (201.487424ms)
✔ resolve: prints one instance's record (281.684478ms)
✔ resolve: refusals (454.610462ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2381.258516
@@ -0,0 +1,21 @@
✔ C2-04: instance settings skip a library while checking a later code project (32.527878ms)
✔ row 165 read lists each instance and the definitions add accepts (3.218296ms)
✔ row 165 add: two instances join, validate exits 0, the log names the human, and the bus restart is reported (93.702292ms)
✔ row 165 change: definition, vars and launch membership log before and after; only a definition change asks for a restart (99.793939ms)
✔ row 165 refuses each validation failure and leaves the file's bytes alone (33.820979ms)
✔ row 165 refuses an added instance whose definition declares credentials, and says why (5.999086ms)
✔ row 165 refuses a definition change to or from one that declares credentials (6.767714ms)
✔ row 165 a vars or launch change on an instance with its binding succeeds and keeps the binding (89.83786ms)
✔ row 165 refuses an END_PROTECTED definition and every change to a protected instance (9.793219ms)
✔ row 165 refuses while a project file doesn't resolve, as validate would (15.487298ms)
✔ row 165 refuses a stale revision and leaves the file's bytes alone (10.871014ms)
✔ row 65 an instance write refuses harness-off, thinking-unsupported, an unknown model and a partial fallback (38.02395ms)
✔ row 213 a creative instance is the human's: add, vars and definition changes, persona digest only (90.045141ms)
ℹ tests 13
ℹ suites 0
ℹ pass 13
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 608.08011
@@ -0,0 +1,12 @@
✔ C2-04 M9: changed playground and hybrid entries leave business and changes.jsonl byte-identical (23.242034ms)
✔ C2-04 M9: code projects, additions, deletions and array changes are protected too (15.756145ms)
✔ C2-04 M9: canonical key order is allowed and a non-project setter still records its change (17.060499ms)
✔ C2-04 M9: a serialization hook cannot change projects after the canonical comparison (9.833495ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 118.54831
@@ -0,0 +1,19 @@
✔ C2-04: a library first in projects does not hide a later project layer (9.584516ms)
✔ systemVars flattens the validated config (1.063324ms)
✔ precedence: system, business, project, project role, agent (2.001802ms)
✔ limits narrow the definition and never widen it (2.563505ms)
✔ role.launch stays within-role only for the instance the launch block names (5.04696ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (2.106162ms)
✔ limits.authority narrows cross-role actions too (1.098761ms)
✔ classify (1.065895ms)
✔ the record carries what the broker and launcher need (0.966677ms)
✔ digest: key order doesn't matter, any value change does (7.43546ms)
✔ refusals (2.150825ms)
ℹ tests 11
ℹ suites 0
ℹ pass 11
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 94.022587
@@ -0,0 +1,54 @@
✔ C2-04: launcher workspace clones code but never the library (1298.398732ms)
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1264.618734ms)
usage: {"event":"usage-unknown","business":"acme","run":"r4accc4841d41","instance":"chief","turn":"81814ac7-9330-484a-95f8-cc2f80a9aeef","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r0719029c31b1","instance":"coder","turn":"fd2dfc33-9ab6-4d4d-9eef-ba46b6d7a510","source":"@none","code":"baseline-unknown"}
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (657.713119ms)
usage: {"event":"usage-unknown","business":"acme","run":"r4b37f983290f","instance":"chief","turn":"6c8768d3-4742-4ae2-87c6-4653182c8520","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r7bc8dc8db66c","instance":"pm","turn":"4b196787-1bf9-4702-82d2-397c84ab1e3c","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r4b37f983290f","instance":"chief","turn":"1c6ec26d-97da-43b6-8cd8-91994e6c7c7b","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r7bc8dc8db66c","instance":"pm","turn":"a12fdeb9-e25d-45a4-bd25-e048621a3557","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r7bc8dc8db66c","instance":"pm","turn":"ad419687-0675-48a8-9932-90b2da5333ef","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r4b37f983290f","instance":"chief","turn":"0d54926f-f5b8-4dbb-b38a-8ea23fdd5cb5","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r7bc8dc8db66c","instance":"pm","turn":"6891e20c-472e-4e67-815e-88728024acf4","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r4b37f983290f","instance":"chief","turn":"9ac6fe37-9044-4f54-bae0-826a6c84c15b","source":"@none","code":"baseline-unknown"}
✔ a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold (1202.248105ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3926.301471ms)
✔ a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none (593.731622ms)
✔ REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing (755.589469ms)
usage: {"event":"usage-unknown","business":"acme","run":"rf7ecf1ef2b65","instance":"coder","turn":"744cb116-e163-4b18-831e-556df7e7bfd9","source":"@none","code":"baseline-unknown"}
✔ a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited (739.551352ms)
✔ a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder (564.790867ms)
✔ after a restart, the new host's first tick starts a holder whose decision is still held (883.94802ms)
✔ a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder (595.491854ms)
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (250.651304ms)
✔ a runner that stops at once ends its launch with the runner's reason (336.620423ms)
✔ a Claude Code launch with no session credential refuses before it starts (172.971844ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (287.021469ms)
usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/herb-row215-on-f827/packages/plugins/harness/down is missing","kept":[]}
plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/herb-row215-on-f827/packages/plugins/harness/down is missing","kept":[]}
✔ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (327.373038ms)
✔ a runner killed from the host ends its launch as killed, not failed (381.927989ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (198.778768ms)
✔ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts (184.44803ms)
✔ row 213 a creative instance's persona is read from <configDir>/personas: off its digest the launch fails with the mismatch as its reason (239.197527ms)
✔ row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts (230.64829ms)
usage: {"event":"usage-unknown","business":"acme","run":"r800039251a09","instance":"chief","turn":"f2e6a1ad-767e-4316-be90-6194b840e2ef","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"rfeeb081c2983","instance":"coder","turn":"1abdb906-521d-4100-897b-f5eba12663cf","source":"@none","code":"baseline-unknown"}
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1612.164523ms)
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (909.716449ms)
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3844.434582ms)
✔ bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was (224.265713ms)
✔ bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned (193.925449ms)
✔ bus start --no-launch starts no thread tick; the same host with launches runs it (1181.783938ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (166.842635ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (171.101521ms)
✔ a launch client that never closes its side doesn't hold the host's close (170.266667ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1305.753454ms)
ℹ tests 31
ℹ suites 0
ℹ pass 31
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 24989.261794
@@ -0,0 +1,33 @@
✔ row 232: confirm, revert, gate, --to and --expect-from need --units external; the user-only flags are usage under it (1129.148785ms)
✔ row 232: pm-on and activate --pm-after-harness refuse external-pm (1112.631619ms)
✔ row 232: units-running (N4) refuses while a unit is active and leaves the store, the log and TAKEN; failed counts as down (2301.278572ms)
✔ row 232: activate stages: the pointer stays, AWAITING-UNITS names TO, a backup of the stopped store, no directory in the set, no --user, no journal (2229.906955ms)
✔ row 232: A3 order: capture-failed and marker-failed refuse with exit 3 and leave no marker or FAILED (1913.7476ms)
✔ row 232: confirm and revert refuse open-activation with no marker, two, the wrong copy, or one the log holds closed; refusals name no activation (3892.845676ms)
✔ row 232: confirm polls: bus status good after 3 s fails with --timeout 1, writes FAILED and leaves the rest; FAILED then refuses activation-failed (3703.021367ms)
✔ row 232: confirm polls to a pass with --timeout 10: the pointer, an ok line that reads as the started run, no marker (6350.955178ms)
✔ row 232: stale-start: @1792540799 fails a start floor of 1792540800 with stale-start (3365.187863ms)
✔ row 232: stale-start is a gate code: an enter time that turns to @1792540800 after 2 s passes with --timeout 10 (4713.287991ms)
✔ row 232: gate (N2, A2f): --since 1792540800 passes @1792540800 and fails @1792540799; it writes nothing (3014.18649ms)
✔ row 232: the external PM check reads launches list: one live run of launch.by since the start passes (7715.019003ms)
✔ row 232: revert puts the wrapper back and leaves the store when nothing migrated (1540.240269ms)
✔ row 232: confirm --rollback refuses rollback-mismatch, and logs rollback-failed with exit 2 when OLD fails its gate (3838.859298ms)
✔ row 232: confirm --rollback after a --restore swap logs rollbackCode not-attempted; rollback --restore finds an external backup (4385.316411ms)
✔ row 232: sequence 1: activate, confirm times out, revert, confirm --rollback --cause A5: gate-failed rollback ok (5668.17255ms)
✔ row 232: sequence 2: activate --migrate, the wrapper install fails, revert restores TAKEN, confirm --rollback --cause A3 (3356.512703ms)
✔ row 232: sequence 3: activate, root's A4 fails, revert, confirm --rollback --cause A4: failed code units-start (3090.998502ms)
✔ row 232: sequence 4: a capture failure leaves nothing open, then A2f's gate OLD writes nothing (2006.820211ms)
✔ row 232: sequence 5: a confirm of the wrong copy is refused, and revert and confirm --rollback still close the activation (3716.790879ms)
✔ row 232: sequence 6: a revert that fails leaves the activation open, and the next --plan refuses interrupted-run (2084.865556ms)
✔ row 232: sequence 7: adopt, two confirmed activates, rollback --to the previous copy, confirmed, logs op rollback (8859.407093ms)
✔ row 232: sequence 8: a migration that writes and fails: revert restores the backup byte for byte, confirm --rollback --cause A3 (4066.89509ms)
✔ row 232: user mode: every systemctl call carries --user, and activate, rollback and check make the base's calls (8472.325246ms)
✔ row 232: user mode: a default activate --plan prints the base's bytes (2725.266801ms)
ℹ tests 25
ℹ suites 0
ℹ pass 25
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 96332.465778
@@ -0,0 +1,227 @@
✔ T1 adopt writes the pointer from the units once; a failing check refuses check-failed (1604.406515ms)
✔ T2 activate installs exactly the pointer's pm, claude and Console flags (2285.234425ms)
▶ T3 a hand-edited unit refuses unit-flags-drift, names the difference and changes nothing
✔ pm (1074.84155ms)
✔ claude (1048.597035ms)
✔ console (1069.472197ms)
✔ T3 a hand-edited unit refuses unit-flags-drift, names the difference and changes nothing (3193.872973ms)
▶ T4 each item 3 refusal changes nothing and logs one line with its code
✔ copy-missing (970.101799ms)
✔ copy-unreachable (976.867501ms)
✔ copy-schema-unknown (942.411453ms)
✔ already-active (966.01391ms)
✔ instance-busy (another business active) (1033.865857ms)
✔ instance-busy (this business activating) (989.106934ms)
✔ drop-in-override (1051.252734ms)
✔ no-pointer (535.256184ms)
✔ T4 each item 3 refusal changes nothing and logs one line with its code (7466.71817ms)
✔ T5 an activate with no migration stops the Console then the bus, installs, starts the bus then the Console (2298.616234ms)
✔ T6 a migration without --migrate rolls back with migration-needs-flag (2085.91559ms)
✔ T7 --migrate takes the backup before the migration runs (2415.592569ms)
✔ T8 a failed migration restores the backup and keeps the store it replaced (2571.261148ms)
✔ T9 a store outside the target's chain rolls back with store-newer (2405.058696ms)
▶ T10 each gate check failing alone rolls back and puts every file back
✔ units-inactive (5907.690999ms)
✔ bus-status (6093.058453ms)
✔ healthz (6393.330472ms)
✔ copy-check (5442.480521ms)
✔ pm-launch-unconfirmed (5607.705975ms)
✔ T10 each gate check failing alone rolls back and puts every file back (29445.44482ms)
▶ T10b the PM check needs exactly one launched line for a live run of this start
✔ no launched line (5534.978468ms)
✔ two lines (5372.975087ms)
✔ a changed word (5420.826895ms)
✔ run not in launches list (5601.520776ms)
✔ run started before the start (5448.208485ms)
✔ a human launch with no journal line (5346.70067ms)
✔ run for another business (5684.948726ms)
✔ run not live (5860.307931ms)
✔ one well-formed line and a live run pass (2078.4402ms)
✔ T10b the PM check needs exactly one launched line for a live run of this start (46350.52861ms)
✔ T11 a rollback whose gate fails too: rollback-failed, exit 2, both units stopped, no retry (7145.208417ms)
✔ T12 rollback takes the previous copy from the log (3164.021139ms)
✔ T13 a rollback across a migration needs --restore, then restores and goes live (3796.056873ms)
✔ T14 events written after the backup need --events N with the right N (4384.802883ms)
✔ T14b a rollback --restore whose gate fails stops both units and skips item 6 (5813.087127ms)
✔ T14c a rollback without --restore whose gate fails runs item 6 (6237.609182ms)
✔ T15 --plan names the migration and the restore and changes nothing (2849.139286ms)
✔ T26 --plan lists the commits an activate adds, after the migration lines (1481.868533ms)
✔ T27 --plan names each migration step, or says the steps cannot be read (1173.748951ms)
✔ T25 --plan refuses copies-unrelated when neither copy is an ancestor of the other (569.344968ms)
▶ T23 --plan reads the store without changing a file, stopped or held
✔ stopped store (721.626407ms)
✔ held store (690.820461ms)
✔ T23 --plan reads the store without changing a file, stopped or held (1413.175682ms)
✔ T24 business-mismatch and capture-failed refuse with exit 3; a short id is a usage error (1463.46361ms)
✔ T16 status prints the pointer, the log, the check, bus status and drift, and writes nothing (910.07629ms)
✔ T19 a started line with no final line: status shows it and every changing verb refuses (1271.59212ms)
✔ T20 a held run lock refuses run-locked without a log line; status and --plan still run (1515.929929ms)
✔ T21 a bus unit with no MOSAIC_CLAUDE_BIN: adopt and activate refuse no-claude-pin (1789.2511ms)
▶ T22 the stopped store read: a -wal or -shm refuses store-unclean and is left alone; a clean read adds no file
✔ bus.sqlite-wal (1954.988144ms)
✔ bus.sqlite-shm (2042.456191ms)
✔ clean (2209.768749ms)
✔ T22 the stopped store read: a -wal or -shm refuses store-unclean and is left alone; a clean read adds no file (6208.13226ms)
▶ T28 a unit path, drop-in directory or drop-in file that cannot be read, or a relative unit path, refuses drop-in-override before any stop
✔ unit path (938.058563ms)
✔ drop-in directory (934.896589ms)
✔ drop-in file (958.129787ms)
✔ relative unit path (943.563721ms)
✔ T28 a unit path, drop-in directory or drop-in file that cannot be read, or a relative unit path, refuses drop-in-override before any stop (3775.662901ms)
▶ T29 a bus that stops during the held-store read: --plan says so, rollback --restore refuses restore-mismatch
✔ --plan (932.393863ms)
✔ rollback --restore (2879.958858ms)
✔ T29 a bus that stops during the held-store read: --plan says so, rollback --restore refuses restore-mismatch (3812.923223ms)
✔ T30 a copy check that agrees on another copy fails the gate with copy-check and rolls back (6646.085499ms)
▶ T31 a quoted or escaped Environment= or ExecStart= is unreadable: refusals by name, nothing read from /a
✔ Environment double quotes (1976.227811ms)
✔ Environment single quotes (2063.211816ms)
✔ Environment backslash (2038.183771ms)
✔ ExecStart quotes (1984.451614ms)
✔ Console ExecStart quotes (1980.466835ms)
✔ T31 a quoted or escaped Environment= or ExecStart= is unreadable: refusals by name, nothing read from /a (10043.710809ms)
▶ T32 item 1: --pm-after-harness refuses a target without harness or a file that sets it, and passes without the PM
✔ usage: not with --pm, and only for activate (721.955423ms)
✔ pm-after-harness-target (1015.757752ms)
✔ pm-after-harness-set (1371.242025ms)
✔ a pointer without pm (841.537689ms)
✔ phase 1 installs without --pm, skips pm_launched and writes the pending pointer (1915.010297ms)
✔ T32 item 1: --pm-after-harness refuses a target without harness or a file that sets it, and passes without the PM (5866.555622ms)
▶ T33 item 2: read_pointer takes pmPending only as true beside pm false; a pending pointer refuses activate and shows in status
✔ read_pointer (1570.154285ms)
✔ pm-pending, already-adopted and the status line (3037.065347ms)
✔ T33 item 2: read_pointer takes pmPending only as true beside pm false; a pending pointer refuses activate and shows in status (4607.778248ms)
▶ T34 item 3: pm-on refuses before any stop, passes with the PM, or puts the phase-1 bus back
✔ not-pending (719.010856ms)
✔ harness-unset (2272.485716ms)
✔ a launched line passes: the bus alone restarts with --pm (3276.598137ms)
✔ a failed pm_launched puts the phase-1 unit back: exit 1, still pending (6693.543553ms)
✔ a failed phase-1 gate too: pm-on-down, exit 2, both units stopped (8141.636634ms)
✔ T34 item 3: pm-on refuses before any stop, passes with the PM, or puts the phase-1 bus back (21104.286659ms)
▶ T35 item 4: rollback from pending installs --pm; a plug-in kind the target lacks refuses by name
✔ rollback from pending gates pm_launched on the old copy (3372.092831ms)
✔ after the harness set, rollback refuses until harness is gone (5046.857905ms)
✔ activate names every kind the target lacks (1244.308621ms)
✔ T35 item 4: rollback from pending installs --pm; a plug-in kind the target lacks refuses by name (9663.997607ms)
✔ T36 --plan shows the pending state and lists the row 169 refusals it can see (3695.067003ms)
✔ T37 tester-port activate writes pointer and unit, replaces the port, and supported re-pin and rollback keep it (6897.072525ms)
✔ T38 tester-port rollback to unsupported target drops flag in plan, steps, unit and pointer (3812.546819ms)
✔ T39 tester-port explicit unsupported target reads target copy and refuses before stopping either unit (904.276249ms)
✔ T40 tester-port invalid values refuse exit 4 before stopping, including tunnel and main ports (819.160889ms)
✔ T41 tester-port inherited activate to unsupported target drops it and retains drift refusal and failed-run recovery (8462.827367ms)
✔ T42 tester-port pm-on preserves pointer Console flags (3200.83272ms)
▶ T43 row 180 item 1: runs in one second get distinct activation ids, rollback sets and post-attempt stores
✔ activate then pm-on on the same copy (3520.417628ms)
✔ two restores: an old-form id and a suffixed one, each with its own post-attempt store (6507.524064ms)
✔ adopt takes the next free id too (703.050442ms)
✔ all ten names taken: capture-failed before anything stops, and the log line names no activation (956.29941ms)
✔ --restore takes an id with or without -1 to -9 and nothing else (971.565868ms)
✔ T43 row 180 item 1: runs in one second get distinct activation ids, rollback sets and post-attempt stores (12659.952034ms)
▶ T44 row 180 item 2: --plan lists tester-port-invalid and tester-port-target with its other refusals
✔ a bad tester port (1970.260005ms)
✔ without --plan a bad port is usage before any pointer read (650.605696ms)
✔ a target Console without --tester-port (1005.8136ms)
✔ T44 row 180 item 2: --plan lists tester-port-invalid and tester-port-target with its other refusals (3627.354723ms)
▶ T45 row 181 items 2 and 3: the restore guard counts events and every COUNTED table
✔ both modes count events and each listed table, and a table the store lacks counts 0 (817.850391ms)
✔ one instance_names row and no event refuses before any stop; --events 1 restores (4258.491111ms)
✔ one usage_records row and no event refuses before any stop; --events 1 restores (4200.77682ms)
✔ a listed table that cannot be read refuses restore-mismatch before any stop (2308.610496ms)
✔ the in-run recount counts the listed tables too (3113.596786ms)
✔ T45 row 181 items 2 and 3: the restore guard counts events and every COUNTED table (14700.29833ms)
✔ T46 row 181 item 4: every table in the bus schema is in exactly one of four lists (3.037176ms)
✔ T47 row 185 test 1: a same-schema activate leaves a whole backup and the log line names it (1868.973993ms)
✔ T48 row 185 test 2: a gate failure on a same-schema activate rolls back without a restore (5804.760552ms)
▶ T49 row 185 test 3: a same-schema rollback --restore restores, or refuses rows written after STAMP
✔ no rows after STAMP: restores and goes live (3320.288695ms)
✔ one instance_names row after STAMP refuses post-backup-events; --events 1 restores (3651.108762ms)
✔ T49 row 185 test 3: a same-schema rollback --restore restores, or refuses rows written after STAMP (6972.06843ms)
✔ T50 row 185 test 4: activate --plan prints the backup line and writes nothing (2352.202566ms)
✔ T51 row 185 test 5: an activate with no store file takes no backup (2180.71818ms)
▶ T52 row 186: the activate plan's backup line says what the run will do
✔ same schema (2482.119077ms)
✔ same schema with --migrate (2194.383877ms)
✔ in chain with --migrate (2161.378827ms)
✔ in chain without --migrate (1945.096692ms)
✔ outside the chain (1933.991036ms)
✔ outside the chain with --migrate (1982.219098ms)
✔ T52 row 186: the activate plan's backup line says what the run will do (12700.35568ms)
✔ T178 proxy proof header and path survive re-pin and rollback unchanged in the pointer and unit (3084.56264ms)
✔ T178 proxy proof pair survives a tester-port replacement as well (1871.352274ms)
▶ T53 row 187: activate sets, carries and drops the proof pair
✔ tests 1 and 9: the pair goes after the preview flags, before --send and the kept --tester-port, as the unit renders it; a new pair replaces it (5957.458612ms)
✔ tests 2 and 3: an activate of the live copy writes the pair; a later activate and a rollback carry it (4748.919152ms)
✔ test 2: only a pair change may name the live copy; the log keeps from and to, and rollback still goes to the earlier copy (6348.550383ms)
✔ test 2: a live-copy activate says only the Console flags change, and backs up the stopped store before it installs (3766.45236ms)
✔ test 3: pm-on keeps the pair (2959.180793ms)
✔ test 4: --no-proof drops the pair from the pointer and the unit and says so; with no pair it says so and passes (5233.036939ms)
✔ test 5: each refusal exits 3 before anything stops (3649.68534ms)
✔ tests 5 and 9: --plan lists every proof refusal with the others and writes nothing (836.025341ms)
✔ test 6: usage errors (773.25631ms)
✔ test 7: a rollback to a copy without proof support drops the pair and says so (3311.183201ms)
✔ test 7: an activate to a copy without proof support refuses proof-target without --no-proof and passes with it (3616.1202ms)
✔ T53 row 187: activate sets, carries and drops the proof pair (41201.876404ms)
▶ T54 row 188: a carried pair gets the proof file checks, and a proof file inside the repository refuses
✔ test 1: an activate that carries the pair refuses after chmod 644, in the plan and the run, before anything stops (2194.421439ms)
✔ test 1: an activate that carries the pair refuses after a symlink to a mode-600 file, in the plan and the run, before anything stops (2189.529552ms)
✔ test 1: an activate that carries the pair refuses after removal, in the plan and the run, before anything stops (2243.035352ms)
✔ test 2: a rollback that carries the pair refuses after chmod 644, in the plan and the run, before anything stops (2336.000835ms)
✔ test 3: a proof file inside the repository refuses, in the plan and the run, before anything stops (1105.046097ms)
✔ test 4: a path outside the repository through a directory symlink into it refuses on its realpath (1150.666433ms)
✔ T54 row 188: a carried pair gets the proof file checks, and a proof file inside the repository refuses (11219.876632ms)
▶ T55 row 189: activate --no-send drops --send, and rollback and pm-on carry it off
✔ test 1: a live-copy --no-send drops --send from the pointer and the unit, keeps the rest in order, logs the step and backs up the stopped store (2274.3857ms)
✔ test 2: a plain live-copy activate still refuses already-active, and so does --no-send once --send is gone (3110.763383ms)
✔ test 3: --plan prints one send: line in each case, with the live-copy note on a same-copy run (4485.563548ms)
✔ test 4: after --no-send, a rollback to the earlier copy leaves --send out of the pointer and the unit (3260.741012ms)
✔ test 4: pm-on after --no-send keeps --send off (3151.996202ms)
✔ test 5: --no-send on rollback, pm-on, status, adopt or rehearse is a usage error (654.253174ms)
✔ test 6: with both a pair and --send in the pointer, --no-send and --no-proof each drop what they name and keep the rest (9881.189087ms)
✔ T55 row 189: activate --no-send drops --send, and rollback and pm-on carry it off (26820.278319ms)
▶ T57 row 200: activate --sso, its refusals before any stop, and the rollback to a Console without --sso
✔ item 1: --sso drops the preview set and the pair, puts --sso before --send and the tester port, and the plan names what it drops and carries (3703.052075ms)
✔ item 1: --sso may re-pin the live copy once; later activates carry it; --no-send still drops --send (8819.731338ms)
✔ item 1: --sso with a pair or --no-proof, or on any verb but activate, is a usage error (692.195455ms)
✔ item 2: a target without --sso, a missing or bad console.sso, and each slot problem refuse before any stop (5662.393194ms)
✔ item 3: a config whose console key the target loader refuses stops an activate or a rollback before any stop, naming the first step (4284.150525ms)
✔ item 3: a rollback from --sso to a Console without it takes the flags from the set of the activation onto the copy, past a same-copy re-pin (sage-1389) (11448.283647ms)
✔ item 3: a rollback set that is missing, fails its sums, holds no argv or already ran --sso refuses sso-rollback-flags (4548.577973ms)
✔ round 2 (webby-0028 N1): run through a symlinked repository path, a slot or a proof file inside the repository still refuses (2401.295904ms)
✔ round 2 (webby-0028 N2): a config.json that does not parse stops the run before anything is read or stopped (2172.272961ms)
✔ round 3 (sage-1556): a named instance turns --sso on with its own config, slot and Console unit, and rolls back from its own set (4291.803296ms)
✔ row 238 (#1744): on a named instance the sso-config first step names --instance NAME in both commands (1134.427878ms)
✔ T57 row 200: activate --sso, its refusals before any stop, and the rollback to a Console without --sso (49160.357454ms)
✔ R1 rehearse passes on the same schema; the scratch root holds its files and the live files stay byte-identical (3480.635997ms)
✔ R2 rehearse runs the migration checks when the schema differs; the live store stays at 3g (3538.664935ms)
✔ R3 a failing migration gives REHEARSAL_FAIL migrate and starts nothing (352.394887ms)
✔ R4 a host that does not start, does not answer status, reports another pid, launches anyway, makes a launches directory or keeps its lock each fails by name (22497.071985ms)
✔ R5 a host that ignores SIGTERM gets SIGKILL and FAIL host-stopped (4425.852712ms)
✔ R6 a Console that does not answer /healthz gives REHEARSAL_FAIL healthz (5628.652507ms)
✔ R7 a host on another copy fails copy-match (5626.942239ms)
✔ R8 a scratch port in use or reserved refuses, and the rehearsal takes no other port (483.876429ms)
✔ R9 mos-0963: a role reference left outside placeholders/, or left on an env variable, fails credential-ref-live before the host starts (737.183647ms)
✔ R10 mos-0962: a scratch business that still sets a tracker fails tracker-configured (336.641991ms)
✔ R11 mos-0961: a scratch config on the live data root or on any root but the scratch one, or a sessions.json naming a running pid, refuses by name (1329.146892ms)
✔ R12 mos-0962: a notifier config with a binding fails notify-off (322.530573ms)
✔ R13 the pre-checks: an old copy, a backup that fails its sums, a missing backup or pointer, and usage (467.892209ms)
✔ R14 a host that writes the live config, the live business file or the backup fails the end check by name (9332.612807ms)
✔ R15 a rehearsal stopped by TERM, INT or HUP fails signal and still stops the host and Console and runs the end checks (9980.867141ms)
✔ row 233: --backup last takes the newest backup the log records; a later line with backup null does not hide it, and the store line names it (3438.884089ms)
✔ row 233: --backup last with no backup in the log, or no log, exits 3 with no-backup and makes no rehearsal directory (190.873827ms)
✔ row 233: --backup last refuses a newest backup that is not an activation id, with exit 1 (117.903969ms)
▶ T56 row 190: named instances side by side on one unit directory, $BIN and set of stand-ins
✔ T-I3 each instance renders its own config, session env and Description, and nothing of another (1497.847969ms)
✔ T-I4 a Console port another installed Console takes refuses port-claimed, and a named instance keeps off the default ports (1995.198242ms)
✔ T-I6 alpha's activate and rollback reach nothing of beta's (3765.448219ms)
✔ T-I6 swapped: beta's reach nothing of alpha's (3738.069466ms)
✔ T-I7 MOSAIC_CONFIG naming alpha's config with no flag refuses instance-root-mismatch and changes nothing (672.561735ms)
✔ T-I9 another instance's bus active refuses nothing; one of this instance's does (5819.537565ms)
✔ T56 row 190: named instances side by side on one unit directory, $BIN and set of stand-ins (17490.000329ms)
ℹ tests 196
ℹ suites 0
ℹ pass 196
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 533493.817869
@@ -0,0 +1,29 @@
✔ row 233: sequence 1: confirm fails healthz, the driver reverts, renders the old copy and confirm --rollback --cause A5 passes: exit 1 (6210.373434ms)
✔ row 233: sequence 2: activate --migrate fails at the wrapper, revert restores the store, confirm --rollback --cause A3: exit 1 (3236.556603ms)
✔ row 233: sequence 3: root's render of the new copy fails, the driver rolls back with --cause A4, and holds the lock to the end: exit 1 (3088.506825ms)
✔ row 233: sequence 4: a capture failure is exit 3 from the stage, and A2f restarts the old units and gates them: exit 1 (2296.722984ms)
✔ row 233: N1: a bus that outlives A2's timeout keeps its old start, so A2f's gate fails stale-start: exit 2, both units stopped (7977.855045ms)
✔ row 233: sequence 5: confirm of the wrong copy is refused, and the driver rolls back with --cause A5: exit 1 (3484.271196ms)
✔ row 233: sequence 6: a revert that fails stops both units at exit 2, and the next run refuses at its --plan with nothing stopped (2709.834241ms)
✔ row 233: sequence 7: two upgrades and a rollback --to the previous copy with --restore, the args file following each confirm (7481.097653ms)
✔ row 240: I3 N1: a failed args move after A5 exits 5 args-failed, the new copy live and confirmed and both args files kept (2791.714439ms)
✔ row 233: sequence 8: a migration that writes and fails: the driver reverts to the backup byte for byte, --cause A3: exit 1 (3155.287145ms)
✔ row 233: a second run while the lock is held refuses upgrade-running with one log line and no call (659.849042ms)
✔ row 233: A1 refuses open-activation on any AWAITING-UNITS file, through the stack's user's --plan, with nothing stopped (1064.03698ms)
✔ row 233: a target that migrates refuses migrate-required without --migrate, with nothing stopped (1067.011634ms)
✔ row 233: the rehearsal runs after the plan and before A2; a failed one refuses rehearsal-failed with nothing stopped (1099.022074ms)
✔ row 233: the rehearsal's BACKUP_STORE line goes to the step log after a match, a malformed one as none, the rest of its output never (2918.095196ms)
✔ row 233: a real rehearsal with no backup in the log refuses no-backup, and goes on only with --no-rehearsal, which logs the skip (3400.448853ms)
✔ row 233: a real rehearsal after an upgrade names the log's newest backup; --no-rehearsal with a backup refuses rehearsal-required (3795.598553ms)
✔ row 233: the args file must be the running user's, with no group or other write, one --copy, and the copy the units run (839.804685ms)
✔ row 233: hostcheck (row 240): a nonzero --check or a missing file refuses profile-drift before the plan (962.315692ms)
✔ row 233: the checkout must be at --sha (head-mismatch), and a commit the clone can't fast-forward to refuses fetch-failed (936.914531ms)
✔ row 233: usage exits 4 before any log or lock (685.887579ms)
ℹ tests 21
ℹ suites 0
ℹ pass 21
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 60456.446459
@@ -0,0 +1,12 @@
✔ C2-04: a library-only business with tracker.baseUrl boots without a project tracker (143.257728ms)
task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200
task.close on a missing task answered: task-not-found; it made GET /tasks/999 404
✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (274.054339ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 502.723687
@@ -0,0 +1,24 @@
✔ C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization (35.180686ms)
✔ C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths (28.575775ms)
✔ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (100.935123ms)
✔ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (11.206477ms)
✔ C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock (20.077192ms)
✔ C2-04 state: missing state is empty only while the served ref is absent (32.816347ms)
✔ C2-04 state: valid problems and sync metadata return no content and change no state bytes (34.864084ms)
✔ C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed (97.291867ms)
✔ C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement (29.030379ms)
✔ C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text (71.409306ms)
✔ C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies (78.061049ms)
✔ C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes (230.30816ms)
✔ C2-04 lock: same-process waits, timeout code, callback expiry and throw release (5027.25259ms)
✔ C2-04 lock: owner death releases the kernel lock without replacing its file (52.471475ms)
✔ C2-04 lock: a missing flock binary becomes a handled refusal (44.850141ms)
✔ C2-04 lock: an unawaited scoped read drains before the lock is released (19.645975ms)
ℹ tests 16
ℹ suites 0
ℹ pass 16
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5963.543966
@@ -0,0 +1,23 @@
✔ C2-04: record APIs refuse a library before creating a repository (2.136303ms)
✔ init creates a checked versioned layout, valid templates and human-authored local history (25.282114ms)
✔ init honors repo, refuses a non-empty directory and an undeclared project without touching files (13.067957ms)
✔ check reports every bad record, filename mismatch and unknown top-level path (11.851979ms)
✔ one writer commits validated docs, requirements and decisions with the bound agent author (66.653ms)
✔ writer refuses spec paths, bad schemas, mismatched IDs and invalid actor metadata before changing history (14.848593ms)
✔ writer refuses human pending edits and remotes instead of absorbing or contacting them (18.544136ms)
✔ links, repo/code overlap, identity mismatch and a busy writer refuse safely (17.705717ms)
✔ schemas remain package-owned and reject invalid nested data and dates (0.249795ms)
✔ git commit failure restores only the attempted record and leaves human history unchanged (36.217826ms)
✔ git hooks and ambient git identity cannot alter the record writer or execute outside actions (25.483658ms)
✔ project-local schemas cannot weaken checks; invalid UTF-8 and linked git directories refuse (14.206204ms)
✔ schema constraints and layout omissions are reported independently (12.868115ms)
✔ unsupported schema keywords, malformed contexts, ignored local config and nonempty markers refuse (18.523225ms)
✔ a linked git directory is refused before a writer lock can touch its target (30.574055ms)
ℹ tests 15
ℹ suites 0
ℹ pass 15
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 355.86586
@@ -0,0 +1,91 @@
✔ C2-04: ensureCheckouts skips a library before a real clone and leaves no books path (22.355495ms)
✔ a first launch clones the root at its branch, with the root's path as origin (23.522928ms)
✔ the clone is not at its place until git clone has finished (19.304369ms)
✔ a later launch fast-forwards a clean clone after a commit to the root, then finds it current (56.365259ms)
✔ a clone ahead of the root keeps its commits and counts as current (35.573942ms)
▶ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves
✔ a modified file (41.519272ms)
✔ an untracked file (42.31579ms)
✔ a staged file (42.328417ms)
✔ another branch (40.470049ms)
✔ a detached HEAD (40.702516ms)
✔ a dirty clone and one on another branch are fetched, and nothing but refs/remotes moves (208.344457ms)
✔ a diverged clone gets the fetch into refs/remotes and is kept, not-fast-forward (42.026437ms)
▶ a clone kept before the branch check, or for a failed status, gets no fetch
✔ not-a-clone (22.404264ms)
✔ hooks-present (23.073574ms)
✔ submodule (30.027272ms)
✔ config-changed (26.84014ms)
✔ root-unreadable (27.464656ms)
✔ root-detached (32.242501ms)
✔ status-failed, on the root's branch and clean (35.760734ms)
✔ status-failed, dirty (35.340017ms)
✔ a clone kept before the branch check, or for a failed status, gets no fetch (234.251087ms)
✔ a dirty clone whose fetch fails is kept, fetch-failed, exactly as it was (37.035833ms)
✔ every git call the host makes, for a new clone and an existing one, carries maintenance.auto=false and gc.auto=0 (44.392371ms)
✔ a clone with loose objects enough for gc --auto gets no maintenance or gc from the fetch and the merge (71.033894ms)
▶ a clone with user.name or user.email in its config is kept, and git runs nothing in it
✔ user.name (25.544784ms)
✔ user.email (25.14754ms)
✔ a clone with user.name or user.email in its config is kept, and git runs nothing in it (51.146879ms)
✔ a commit git can't read after the fast-forward is null (45.746486ms)
✔ a new clone whose commit can't be read refuses the launch and leaves nothing behind (18.576825ms)
▶ a clone the session rigged is kept, and git runs nothing and writes nothing outside it
✔ core.fsmonitor (33.120199ms)
✔ core.hooksPath (33.027016ms)
✔ a hook file (30.549575ms)
✔ include.path (33.355749ms)
✔ includeIf (33.915275ms)
✔ a filter driver (34.236367ms)
✔ origin moved (34.552998ms)
✔ a second origin url (33.217597ms)
✔ the origin url twice, both the root (34.870837ms)
✔ an ext:: origin (33.6526ms)
✔ a branch tracking another remote (33.582081ms)
✔ a branch merging a tag (33.08648ms)
✔ a fetch line onto local branches (34.366694ms)
✔ no origin (33.938644ms)
✔ a config file over the size cap (31.285987ms)
✔ config a symlink to /dev/zero (31.094858ms)
✔ the clone a symlink to the bait (32.754738ms)
✔ the clone a file (31.24085ms)
✔ the clone a plain directory (30.973366ms)
✔ .git a gitdir: file (31.404094ms)
✔ .git a symlink (30.769522ms)
✔ .git/logs a symlink (30.883317ms)
✔ .git/refs/heads a symlink (31.770074ms)
✔ .git/objects a symlink (30.840803ms)
✔ a reflog hard-linked to a file outside (30.322982ms)
✔ HEAD a fifo (31.496048ms)
✔ a commondir file (30.352537ms)
✔ an alternates file (30.782307ms)
✔ a gitlink staged, no .gitmodules (43.468245ms)
✔ a gitlink and .gitmodules staged (44.893483ms)
✔ a gitlink to a repository with its own gitlink (51.849481ms)
✔ a gitlink and .gitmodules committed (47.718755ms)
✔ a gitlink and submodule.active in config (44.861481ms)
✔ a .git/modules with no gitlink (33.279554ms)
✔ a clone the session rigged is kept, and git runs nothing and writes nothing outside it (1175.645437ms)
▶ a root that can't be cloned refuses with the project id and leaves nothing behind
✔ missing (10.161685ms)
✔ not a repository (9.929001ms)
✔ a plain directory inside another repository (14.613067ms)
✔ unreadable (9.85915ms)
✔ on no branch (11.8808ms)
✔ missing an object (15.129485ms)
✔ a root that can't be cloned refuses with the project id and leaves nothing behind (72.336456ms)
✔ a clone whose root is on no branch, or gone, is kept and named (28.236024ms)
✔ no credential or forge remote of the root reaches a clone (47.533999ms)
✔ every project in the business file gets its own clone at its own branch (36.497038ms)
✔ a clone past its time gets SIGTERM and refuses the launch, with nothing left in the workspace (513.397369ms)
✔ a clone that ignores SIGTERM gets SIGKILL once the grace period is over (1018.126958ms)
✔ a timeout signals git's process group, so a process git started dies with it (515.579592ms)
✔ once a signalled git has closed, its group gets SIGKILL, so a process that ignored SIGTERM dies too (515.62296ms)
ℹ tests 78
ℹ suites 0
ℹ pass 78
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 4880.907448
@@ -0,0 +1,9 @@
✔ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (109.519835ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 191.536271
@@ -0,0 +1 @@
{"processes": [], "removed": "/mnt/storage/scratch/gt-j5nm6iqn", "sourceHashesUnchanged": 29}
@@ -0,0 +1,5 @@
{
"base": "f8276c5eb",
"tip": "8a04f4aa4aba493eb89e92aa9e0c34084be7058e",
"overlap": []
}
@@ -0,0 +1 @@
2026-10-21T13:27:27.279728+00:00
@@ -0,0 +1,194 @@
[
{
"file": "packages/bus/tests/library.test.mjs",
"rc": 0,
"counts": {
"tests": 6,
"pass": 6,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "01-packages_bus_tests_library.test.mjs.log"
},
{
"file": "packages/bus/tests/project-runtime.test.mjs",
"rc": 0,
"counts": {
"tests": 3,
"pass": 3,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "02-packages_bus_tests_project-runtime.test.mjs.log"
},
{
"file": "packages/business/tests/business.test.mjs",
"rc": 0,
"counts": {
"tests": 19,
"pass": 19,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "03-packages_business_tests_business.test.mjs.log"
},
{
"file": "packages/business/tests/cli.test.mjs",
"rc": 0,
"counts": {
"tests": 10,
"pass": 10,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "04-packages_business_tests_cli.test.mjs.log"
},
{
"file": "packages/business/tests/instance-settings.test.mjs",
"rc": 0,
"counts": {
"tests": 13,
"pass": 13,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "05-packages_business_tests_instance-settings.test.mjs.log"
},
{
"file": "packages/business/tests/library-projects-protected.test.mjs",
"rc": 0,
"counts": {
"tests": 4,
"pass": 4,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "06-packages_business_tests_library-projects-protected.test.mjs.log"
},
{
"file": "packages/business/tests/resolve.test.mjs",
"rc": 0,
"counts": {
"tests": 11,
"pass": 11,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "07-packages_business_tests_resolve.test.mjs.log"
},
{
"file": "packages/cli/tests/launcher.test.mjs",
"rc": 0,
"counts": {
"tests": 31,
"pass": 31,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "08-packages_cli_tests_launcher.test.mjs.log"
},
{
"file": "packages/cli/tests/stack-release-external.test.mjs",
"rc": 0,
"counts": {
"tests": 25,
"pass": 25,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "09-packages_cli_tests_stack-release-external.test.mjs.log"
},
{
"file": "packages/cli/tests/stack-release.test.mjs",
"rc": 0,
"counts": {
"tests": 196,
"pass": 196,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "10-packages_cli_tests_stack-release.test.mjs.log"
},
{
"file": "packages/cli/tests/stack-upgrade.test.mjs",
"rc": 0,
"counts": {
"tests": 21,
"pass": 21,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "11-packages_cli_tests_stack-upgrade.test.mjs.log"
},
{
"file": "packages/cli/tests/trackers-boot.test.mjs",
"rc": 0,
"counts": {
"tests": 2,
"pass": 2,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "12-packages_cli_tests_trackers-boot.test.mjs.log"
},
{
"file": "packages/library/tests/mirror.test.mjs",
"rc": 0,
"counts": {
"tests": 16,
"pass": 16,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "13-packages_library_tests_mirror.test.mjs.log"
},
{
"file": "packages/project/tests/project.test.mjs",
"rc": 0,
"counts": {
"tests": 15,
"pass": 15,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "14-packages_project_tests_project.test.mjs.log"
},
{
"file": "packages/seat/tests/checkout.test.mjs",
"rc": 0,
"counts": {
"tests": 78,
"pass": 78,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "15-packages_seat_tests_checkout.test.mjs.log"
},
{
"file": "packages/webui/tests/library-cli.test.mjs",
"rc": 0,
"counts": {
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "16-packages_webui_tests_library-cli.test.mjs.log"
}
]
@@ -0,0 +1 @@
/mnt/storage/scratch/gt-j5nm6iqn
@@ -0,0 +1 @@
2026-10-21T13:15:15.548778+00:00
@@ -0,0 +1 @@
{"tests": 451, "pass": 451, "fail": 0, "cancelled": 0, "skipped": 0}
@@ -0,0 +1,14 @@
✔ C2-04 M4: both verbs reject every agent before arguments or adapter access (31.111616ms)
✔ C2-04 bus: human reads validate keys and return adapter results without path leakage (12.907709ms)
✔ C2-04 socket: each async library verb resolves its result rather than serializing {} (14.631614ms)
✔ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (42.32493ms)
✔ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (20.701022ms)
✔ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (82.322654ms)
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 342.882774
@@ -0,0 +1,11 @@
✔ Broker keeps the sandbox client import closure: only the listed imports, no lazy project import (1.629219ms)
✔ without a trusted project adapter all three async paths refuse project-not-configured (43.976856ms)
✔ real startBroker injects the project adapter for socket reads and bound-author record writes (68.038516ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 209.393305
@@ -0,0 +1,26 @@
✔ C2-04: library mappings load without roots, remain frozen, and retain mapping refusals (9.717487ms)
✔ C2-04: an otherwise valid publish grant cannot name a library repo (1.932435ms)
✔ config directory and file path follow MOSAIC_CONFIG (0.219374ms)
✔ the fixture business validates and comes back frozen (0.988926ms)
✔ two instances may share a definition (0.940973ms)
✔ top-level refusals (3.202519ms)
✔ arbiters and projects (4.893652ms)
✔ role instances (2.749701ms)
✔ Vikunja bots (5.88131ms)
✔ a role without Vikunja takes no tracker block (4.097438ms)
✔ credential references match the definition's services (2.14689ms)
✔ launch (7.492201ms)
✔ REQ-THREAD-2 REQ-LAUNCH-1 launch.threadIdleSeconds: absent is the default, a positive integer is used, anything else refuses (4.633228ms)
✔ loadBusiness: file checks (1.761847ms)
✔ loadBusiness: not a regular file (36.320112ms)
✔ loading writes nothing (1.117059ms)
✔ names that are Object.prototype properties don't count as declared (2.898886ms)
✔ the shipped example refuses as written and validates once filled in (0.943369ms)
ℹ tests 18
ℹ suites 0
ℹ pass 18
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 152.091247
@@ -0,0 +1,18 @@
✔ C2-04: business validate and resolve skip a library before code projects and credential checks (219.31729ms)
✔ usage errors exit 4 (321.939555ms)
✔ validate: a good business exits 0 and prints instance digests (76.14497ms)
✔ validate: project files (353.409636ms)
✔ validate: missing files and a broken system config (238.885921ms)
✔ validate: credential reference problems exit 2 and name each one (72.187146ms)
✔ validate: a token file inside the repository is refused (66.603163ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (212.341091ms)
✔ resolve: prints one instance's record (340.865306ms)
✔ resolve: refusals (526.732262ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 2493.367293
@@ -0,0 +1,20 @@
✔ C2-04: instance settings skip a library while checking a later code project (33.384562ms)
✔ row 165 read lists each instance and the definitions add accepts (2.667885ms)
✔ row 165 add: two instances join, validate exits 0, the log names the human, and the bus restart is reported (87.186598ms)
✔ row 165 change: definition, vars and launch membership log before and after; only a definition change asks for a restart (92.493768ms)
✔ row 165 refuses each validation failure and leaves the file's bytes alone (29.566007ms)
✔ row 165 refuses an added instance whose definition declares credentials, and says why (3.207564ms)
✔ row 165 refuses a definition change to or from one that declares credentials (4.215516ms)
✔ row 165 a vars or launch change on an instance with its binding succeeds and keeps the binding (72.107052ms)
✔ row 165 refuses an END_PROTECTED definition and every change to a protected instance (7.281711ms)
✔ row 165 refuses while a project file doesn't resolve, as validate would (13.645202ms)
✔ row 165 refuses a stale revision and leaves the file's bytes alone (10.119096ms)
✔ row 65 an instance write refuses harness-off, thinking-unsupported, an unknown model and a partial fallback (29.171515ms)
ℹ tests 12
ℹ suites 0
ℹ pass 12
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 450.444507
@@ -0,0 +1,12 @@
✔ C2-04 M9: changed playground and hybrid entries leave business and changes.jsonl byte-identical (20.276877ms)
✔ C2-04 M9: code projects, additions, deletions and array changes are protected too (16.209877ms)
✔ C2-04 M9: canonical key order is allowed and a non-project setter still records its change (13.103686ms)
✔ C2-04 M9: a serialization hook cannot change projects after the canonical comparison (12.598611ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 112.92317
@@ -0,0 +1,19 @@
✔ C2-04: a library first in projects does not hide a later project layer (6.304379ms)
✔ systemVars flattens the validated config (0.828852ms)
✔ precedence: system, business, project, project role, agent (1.532829ms)
✔ limits narrow the definition and never widen it (2.353655ms)
✔ role.launch stays within-role only for the instance the launch block names (4.439921ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (1.815884ms)
✔ limits.authority narrows cross-role actions too (0.827532ms)
✔ classify (0.829892ms)
✔ the record carries what the broker and launcher need (0.755977ms)
✔ digest: key order doesn't matter, any value change does (5.452ms)
✔ refusals (1.724971ms)
ℹ tests 11
ℹ suites 0
ℹ pass 11
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 79.941483
@@ -0,0 +1,53 @@
✔ C2-04: launcher workspace clones code but never the library (1262.341325ms)
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1237.473965ms)
usage: {"event":"usage-unknown","business":"acme","run":"r4414332d2ef5","instance":"chief","turn":"382a782c-719a-48e8-a249-ec24caf5ad1e","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"reaca93954ae7","instance":"coder","turn":"70bcac05-fbc1-41b2-85cb-725c15884a94","source":"@none","code":"baseline-unknown"}
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (579.516254ms)
usage: {"event":"usage-unknown","business":"acme","run":"rb21267233555","instance":"chief","turn":"c228faeb-6e3c-4453-ba9b-b9a27b9790af","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r1fc01260daa7","instance":"pm","turn":"a5d09ce0-4208-4eb4-8178-6e06a1792ab9","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"rb21267233555","instance":"chief","turn":"25722023-5270-4c50-a8a0-7c3645d08bd7","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r1fc01260daa7","instance":"pm","turn":"f4a76756-4300-4cdf-a64b-542fc075c137","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r1fc01260daa7","instance":"pm","turn":"99ea9441-ce4e-4aab-a56c-614a30c9f0da","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"rb21267233555","instance":"chief","turn":"d85fa6e4-7557-4adf-b1f6-5e76455a1e51","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r1fc01260daa7","instance":"pm","turn":"87f4bf91-2ef2-4fe7-8e99-09fc8b14887e","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"rb21267233555","instance":"chief","turn":"6db9d009-2cb9-41ba-a07e-da13d92b8182","source":"@none","code":"baseline-unknown"}
✔ a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold (917.244851ms)
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (3692.977114ms)
✔ a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none (439.559581ms)
✔ REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing (324.77099ms)
usage: {"event":"usage-unknown","business":"acme","run":"rd33ea739f0c8","instance":"coder","turn":"434622cc-98ff-44b4-99c9-58a8c92f32af","source":"@none","code":"baseline-unknown"}
✔ a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited (502.385034ms)
✔ a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder (453.667705ms)
✔ after a restart, the new host's first tick starts a holder whose decision is still held (654.880099ms)
✔ a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder (498.662638ms)
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (208.782448ms)
✔ a runner that stops at once ends its launch with the runner's reason (261.153743ms)
✔ a Claude Code launch with no session credential refuses before it starts (143.737058ms)
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (239.874115ms)
usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/herb-row215-on212/packages/plugins/harness/down is missing","kept":[]}
plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/herb-row215-on212/packages/plugins/harness/down is missing","kept":[]}
✔ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (281.577833ms)
✔ a runner killed from the host ends its launch as killed, not failed (297.263791ms)
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (151.327191ms)
✔ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts (151.537445ms)
✔ row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts (166.552674ms)
usage: {"event":"usage-unknown","business":"acme","run":"rc42c73baf400","instance":"chief","turn":"e62acc78-0ddd-4cd3-bb90-c828cf5dc76e","source":"@none","code":"baseline-unknown"}
usage: {"event":"usage-unknown","business":"acme","run":"r9fac1139247f","instance":"coder","turn":"e634dd74-b64a-4c8d-8e8d-90244a1070d4","source":"@none","code":"baseline-unknown"}
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1685.572916ms)
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (791.200293ms)
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3731.707174ms)
✔ bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was (196.993732ms)
✔ bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned (159.584006ms)
✔ bus start --no-launch starts no thread tick; the same host with launches runs it (1086.093241ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (132.916552ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (143.651083ms)
✔ a launch client that never closes its side doesn't hold the host's close (144.715063ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1275.914847ms)
ℹ tests 30
ℹ suites 0
ℹ pass 30
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 21918.233853
@@ -0,0 +1,33 @@
✔ row 232: confirm, revert, gate, --to and --expect-from need --units external; the user-only flags are usage under it (832.842347ms)
✔ row 232: pm-on and activate --pm-after-harness refuse external-pm (934.889182ms)
✔ row 232: units-running (N4) refuses while a unit is active and leaves the store, the log and TAKEN; failed counts as down (2121.604528ms)
✔ row 232: activate stages: the pointer stays, AWAITING-UNITS names TO, a backup of the stopped store, no directory in the set, no --user, no journal (1777.227274ms)
✔ row 232: A3 order: capture-failed and marker-failed refuse with exit 3 and leave no marker or FAILED (1712.692045ms)
✔ row 232: confirm and revert refuse open-activation with no marker, two, the wrong copy, or one the log holds closed; refusals name no activation (3642.165244ms)
✔ row 232: confirm polls: bus status good after 3 s fails with --timeout 1, writes FAILED and leaves the rest; FAILED then refuses activation-failed (3522.196896ms)
✔ row 232: confirm polls to a pass with --timeout 10: the pointer, an ok line that reads as the started run, no marker (5641.501857ms)
✔ row 232: stale-start: @1792540799 fails a start floor of 1792540800 with stale-start (3076.294408ms)
✔ row 232: stale-start is a gate code: an enter time that turns to @1792540800 after 2 s passes with --timeout 10 (4471.752137ms)
✔ row 232: gate (N2, A2f): --since 1792540800 passes @1792540800 and fails @1792540799; it writes nothing (2692.190142ms)
✔ row 232: the external PM check reads launches list: one live run of launch.by since the start passes (9781.807961ms)
✔ row 232: revert puts the wrapper back and leaves the store when nothing migrated (1409.775029ms)
✔ row 232: confirm --rollback refuses rollback-mismatch, and logs rollback-failed with exit 2 when OLD fails its gate (3556.739549ms)
✔ row 232: confirm --rollback after a --restore swap logs rollbackCode not-attempted; rollback --restore finds an external backup (3258.873111ms)
✔ row 232: sequence 1: activate, confirm times out, revert, confirm --rollback --cause A5: gate-failed rollback ok (4878.560819ms)
✔ row 232: sequence 2: activate --migrate, the wrapper install fails, revert restores TAKEN, confirm --rollback --cause A3 (3203.155908ms)
✔ row 232: sequence 3: activate, root's A4 fails, revert, confirm --rollback --cause A4: failed code units-start (2844.170694ms)
✔ row 232: sequence 4: a capture failure leaves nothing open, then A2f's gate OLD writes nothing (1940.493148ms)
✔ row 232: sequence 5: a confirm of the wrong copy is refused, and revert and confirm --rollback still close the activation (3423.561744ms)
✔ row 232: sequence 6: a revert that fails leaves the activation open, and the next --plan refuses interrupted-run (1732.106092ms)
✔ row 232: sequence 7: adopt, two confirmed activates, rollback --to the previous copy, confirmed, logs op rollback (8298.40004ms)
✔ row 232: sequence 8: a migration that writes and fails: revert restores the backup byte for byte, confirm --rollback --cause A3 (3658.849346ms)
✔ row 232: user mode: every systemctl call carries --user, and activate, rollback and check make the base's calls (7450.661653ms)
✔ row 232: user mode: a default activate --plan prints the base's bytes (2572.062595ms)
ℹ tests 25
ℹ suites 0
ℹ pass 25
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 89317.249955
@@ -0,0 +1,227 @@
✔ T1 adopt writes the pointer from the units once; a failing check refuses check-failed (1372.806902ms)
✔ T2 activate installs exactly the pointer's pm, claude and Console flags (2175.278069ms)
▶ T3 a hand-edited unit refuses unit-flags-drift, names the difference and changes nothing
✔ pm (940.434448ms)
✔ claude (905.331444ms)
✔ console (953.801488ms)
✔ T3 a hand-edited unit refuses unit-flags-drift, names the difference and changes nothing (2800.414725ms)
▶ T4 each item 3 refusal changes nothing and logs one line with its code
✔ copy-missing (828.258816ms)
✔ copy-unreachable (750.040135ms)
✔ copy-schema-unknown (804.784952ms)
✔ already-active (767.83377ms)
✔ instance-busy (another business active) (836.742584ms)
✔ instance-busy (this business activating) (824.181046ms)
✔ drop-in-override (819.802212ms)
✔ no-pointer (432.597537ms)
✔ T4 each item 3 refusal changes nothing and logs one line with its code (6065.707472ms)
✔ T5 an activate with no migration stops the Console then the bus, installs, starts the bus then the Console (1974.205406ms)
✔ T6 a migration without --migrate rolls back with migration-needs-flag (1689.71261ms)
✔ T7 --migrate takes the backup before the migration runs (1874.080499ms)
✔ T8 a failed migration restores the backup and keeps the store it replaced (1879.088438ms)
✔ T9 a store outside the target's chain rolls back with store-newer (1695.743767ms)
▶ T10 each gate check failing alone rolls back and puts every file back
✔ units-inactive (5145.320966ms)
✔ bus-status (5504.739824ms)
✔ healthz (4630.494167ms)
✔ copy-check (4953.814776ms)
✔ pm-launch-unconfirmed (5175.714883ms)
✔ T10 each gate check failing alone rolls back and puts every file back (25411.164743ms)
▶ T10b the PM check needs exactly one launched line for a live run of this start
✔ no launched line (5309.620588ms)
✔ two lines (5271.450965ms)
✔ a changed word (5207.189363ms)
✔ run not in launches list (5249.94294ms)
✔ run started before the start (5297.104798ms)
✔ a human launch with no journal line (5012.384828ms)
✔ run for another business (5255.258408ms)
✔ run not live (5155.194619ms)
✔ one well-formed line and a live run pass (1936.667118ms)
✔ T10b the PM check needs exactly one launched line for a live run of this start (43696.539474ms)
✔ T11 a rollback whose gate fails too: rollback-failed, exit 2, both units stopped, no retry (9445.48752ms)
✔ T12 rollback takes the previous copy from the log (3164.86878ms)
✔ T13 a rollback across a migration needs --restore, then restores and goes live (3832.433037ms)
✔ T14 events written after the backup need --events N with the right N (4300.182861ms)
✔ T14b a rollback --restore whose gate fails stops both units and skips item 6 (5706.078353ms)
✔ T14c a rollback without --restore whose gate fails runs item 6 (7364.684709ms)
✔ T15 --plan names the migration and the restore and changes nothing (2976.558ms)
✔ T26 --plan lists the commits an activate adds, after the migration lines (1448.921448ms)
✔ T27 --plan names each migration step, or says the steps cannot be read (1074.961775ms)
✔ T25 --plan refuses copies-unrelated when neither copy is an ancestor of the other (504.611631ms)
▶ T23 --plan reads the store without changing a file, stopped or held
✔ stopped store (612.654192ms)
✔ held store (615.45405ms)
✔ T23 --plan reads the store without changing a file, stopped or held (1228.613968ms)
✔ T24 business-mismatch and capture-failed refuse with exit 3; a short id is a usage error (1504.453363ms)
✔ T16 status prints the pointer, the log, the check, bus status and drift, and writes nothing (969.540282ms)
✔ T19 a started line with no final line: status shows it and every changing verb refuses (1272.112268ms)
✔ T20 a held run lock refuses run-locked without a log line; status and --plan still run (1363.537596ms)
✔ T21 a bus unit with no MOSAIC_CLAUDE_BIN: adopt and activate refuse no-claude-pin (1516.277233ms)
▶ T22 the stopped store read: a -wal or -shm refuses store-unclean and is left alone; a clean read adds no file
✔ bus.sqlite-wal (1776.498328ms)
✔ bus.sqlite-shm (1805.948894ms)
✔ clean (1860.970301ms)
✔ T22 the stopped store read: a -wal or -shm refuses store-unclean and is left alone; a clean read adds no file (5444.145497ms)
▶ T28 a unit path, drop-in directory or drop-in file that cannot be read, or a relative unit path, refuses drop-in-override before any stop
✔ unit path (838.2428ms)
✔ drop-in directory (770.669306ms)
✔ drop-in file (787.861386ms)
✔ relative unit path (799.918614ms)
✔ T28 a unit path, drop-in directory or drop-in file that cannot be read, or a relative unit path, refuses drop-in-override before any stop (3197.49908ms)
▶ T29 a bus that stops during the held-store read: --plan says so, rollback --restore refuses restore-mismatch
✔ --plan (842.842612ms)
✔ rollback --restore (2383.664939ms)
✔ T29 a bus that stops during the held-store read: --plan says so, rollback --restore refuses restore-mismatch (3227.016275ms)
✔ T30 a copy check that agrees on another copy fails the gate with copy-check and rolls back (4822.028878ms)
▶ T31 a quoted or escaped Environment= or ExecStart= is unreadable: refusals by name, nothing read from /a
✔ Environment double quotes (1747.115914ms)
✔ Environment single quotes (1747.871141ms)
✔ Environment backslash (1718.42505ms)
✔ ExecStart quotes (1733.916441ms)
✔ Console ExecStart quotes (1805.192313ms)
✔ T31 a quoted or escaped Environment= or ExecStart= is unreadable: refusals by name, nothing read from /a (8753.514814ms)
▶ T32 item 1: --pm-after-harness refuses a target without harness or a file that sets it, and passes without the PM
✔ usage: not with --pm, and only for activate (630.81989ms)
✔ pm-after-harness-target (949.989597ms)
✔ pm-after-harness-set (1400.026643ms)
✔ a pointer without pm (849.389257ms)
✔ phase 1 installs without --pm, skips pm_launched and writes the pending pointer (2037.948918ms)
✔ T32 item 1: --pm-after-harness refuses a target without harness or a file that sets it, and passes without the PM (5869.092521ms)
▶ T33 item 2: read_pointer takes pmPending only as true beside pm false; a pending pointer refuses activate and shows in status
✔ read_pointer (1646.25297ms)
✔ pm-pending, already-adopted and the status line (2899.447167ms)
✔ T33 item 2: read_pointer takes pmPending only as true beside pm false; a pending pointer refuses activate and shows in status (4546.229216ms)
▶ T34 item 3: pm-on refuses before any stop, passes with the PM, or puts the phase-1 bus back
✔ not-pending (781.816005ms)
✔ harness-unset (2528.770078ms)
✔ a launched line passes: the bus alone restarts with --pm (3635.97518ms)
✔ a failed pm_launched puts the phase-1 unit back: exit 1, still pending (6129.763508ms)
✔ a failed phase-1 gate too: pm-on-down, exit 2, both units stopped (10681.440065ms)
✔ T34 item 3: pm-on refuses before any stop, passes with the PM, or puts the phase-1 bus back (23758.959718ms)
▶ T35 item 4: rollback from pending installs --pm; a plug-in kind the target lacks refuses by name
✔ rollback from pending gates pm_launched on the old copy (3432.493856ms)
✔ after the harness set, rollback refuses until harness is gone (4814.40527ms)
✔ activate names every kind the target lacks (1278.267995ms)
✔ T35 item 4: rollback from pending installs --pm; a plug-in kind the target lacks refuses by name (9525.935986ms)
✔ T36 --plan shows the pending state and lists the row 169 refusals it can see (3442.637109ms)
✔ T37 tester-port activate writes pointer and unit, replaces the port, and supported re-pin and rollback keep it (6713.755862ms)
✔ T38 tester-port rollback to unsupported target drops flag in plan, steps, unit and pointer (3556.909655ms)
✔ T39 tester-port explicit unsupported target reads target copy and refuses before stopping either unit (816.518752ms)
✔ T40 tester-port invalid values refuse exit 4 before stopping, including tunnel and main ports (798.151327ms)
✔ T41 tester-port inherited activate to unsupported target drops it and retains drift refusal and failed-run recovery (8335.191178ms)
✔ T42 tester-port pm-on preserves pointer Console flags (3045.995877ms)
▶ T43 row 180 item 1: runs in one second get distinct activation ids, rollback sets and post-attempt stores
✔ activate then pm-on on the same copy (3077.866023ms)
✔ two restores: an old-form id and a suffixed one, each with its own post-attempt store (6478.022663ms)
✔ adopt takes the next free id too (676.463788ms)
✔ all ten names taken: capture-failed before anything stops, and the log line names no activation (927.509173ms)
✔ --restore takes an id with or without -1 to -9 and nothing else (1026.017549ms)
✔ T43 row 180 item 1: runs in one second get distinct activation ids, rollback sets and post-attempt stores (12186.978758ms)
▶ T44 row 180 item 2: --plan lists tester-port-invalid and tester-port-target with its other refusals
✔ a bad tester port (2101.036117ms)
✔ without --plan a bad port is usage before any pointer read (417.577025ms)
✔ a target Console without --tester-port (1050.997021ms)
✔ T44 row 180 item 2: --plan lists tester-port-invalid and tester-port-target with its other refusals (3570.568101ms)
▶ T45 row 181 items 2 and 3: the restore guard counts events and every COUNTED table
✔ both modes count events and each listed table, and a table the store lacks counts 0 (819.811738ms)
✔ one instance_names row and no event refuses before any stop; --events 1 restores (4281.220981ms)
✔ one usage_records row and no event refuses before any stop; --events 1 restores (4339.553222ms)
✔ a listed table that cannot be read refuses restore-mismatch before any stop (3011.194623ms)
✔ the in-run recount counts the listed tables too (3456.814799ms)
✔ T45 row 181 items 2 and 3: the restore guard counts events and every COUNTED table (15910.111456ms)
✔ T46 row 181 item 4: every table in the bus schema is in exactly one of four lists (2.96277ms)
✔ T47 row 185 test 1: a same-schema activate leaves a whole backup and the log line names it (1839.922479ms)
✔ T48 row 185 test 2: a gate failure on a same-schema activate rolls back without a restore (5782.007105ms)
▶ T49 row 185 test 3: a same-schema rollback --restore restores, or refuses rows written after STAMP
✔ no rows after STAMP: restores and goes live (3276.614244ms)
✔ one instance_names row after STAMP refuses post-backup-events; --events 1 restores (3603.193575ms)
✔ T49 row 185 test 3: a same-schema rollback --restore restores, or refuses rows written after STAMP (6880.328466ms)
✔ T50 row 185 test 4: activate --plan prints the backup line and writes nothing (2392.362463ms)
✔ T51 row 185 test 5: an activate with no store file takes no backup (2055.74343ms)
▶ T52 row 186: the activate plan's backup line says what the run will do
✔ same schema (2076.652498ms)
✔ same schema with --migrate (2103.728264ms)
✔ in chain with --migrate (2343.740358ms)
✔ in chain without --migrate (2172.44891ms)
✔ outside the chain (2018.610199ms)
✔ outside the chain with --migrate (2286.782735ms)
✔ T52 row 186: the activate plan's backup line says what the run will do (13003.221353ms)
✔ T178 proxy proof header and path survive re-pin and rollback unchanged in the pointer and unit (3428.609169ms)
✔ T178 proxy proof pair survives a tester-port replacement as well (2005.665148ms)
▶ T53 row 187: activate sets, carries and drops the proof pair
✔ tests 1 and 9: the pair goes after the preview flags, before --send and the kept --tester-port, as the unit renders it; a new pair replaces it (5369.617405ms)
✔ tests 2 and 3: an activate of the live copy writes the pair; a later activate and a rollback carry it (4877.926077ms)
✔ test 2: only a pair change may name the live copy; the log keeps from and to, and rollback still goes to the earlier copy (6411.791056ms)
✔ test 2: a live-copy activate says only the Console flags change, and backs up the stopped store before it installs (3823.440479ms)
✔ test 3: pm-on keeps the pair (2929.528358ms)
✔ test 4: --no-proof drops the pair from the pointer and the unit and says so; with no pair it says so and passes (6134.571168ms)
✔ test 5: each refusal exits 3 before anything stops (4080.818538ms)
✔ tests 5 and 9: --plan lists every proof refusal with the others and writes nothing (921.495619ms)
✔ test 6: usage errors (791.940368ms)
✔ test 7: a rollback to a copy without proof support drops the pair and says so (3569.742095ms)
✔ test 7: an activate to a copy without proof support refuses proof-target without --no-proof and passes with it (4102.029003ms)
✔ T53 row 187: activate sets, carries and drops the proof pair (43015.196531ms)
▶ T54 row 188: a carried pair gets the proof file checks, and a proof file inside the repository refuses
✔ test 1: an activate that carries the pair refuses after chmod 644, in the plan and the run, before anything stops (2443.927487ms)
✔ test 1: an activate that carries the pair refuses after a symlink to a mode-600 file, in the plan and the run, before anything stops (2451.543098ms)
✔ test 1: an activate that carries the pair refuses after removal, in the plan and the run, before anything stops (2371.886229ms)
✔ test 2: a rollback that carries the pair refuses after chmod 644, in the plan and the run, before anything stops (2440.808004ms)
✔ test 3: a proof file inside the repository refuses, in the plan and the run, before anything stops (1028.022624ms)
✔ test 4: a path outside the repository through a directory symlink into it refuses on its realpath (1092.396277ms)
✔ T54 row 188: a carried pair gets the proof file checks, and a proof file inside the repository refuses (11829.775747ms)
▶ T55 row 189: activate --no-send drops --send, and rollback and pm-on carry it off
✔ test 1: a live-copy --no-send drops --send from the pointer and the unit, keeps the rest in order, logs the step and backs up the stopped store (2297.315751ms)
✔ test 2: a plain live-copy activate still refuses already-active, and so does --no-send once --send is gone (3372.219555ms)
✔ test 3: --plan prints one send: line in each case, with the live-copy note on a same-copy run (4822.032599ms)
✔ test 4: after --no-send, a rollback to the earlier copy leaves --send out of the pointer and the unit (3681.502406ms)
✔ test 4: pm-on after --no-send keeps --send off (3384.224907ms)
✔ test 5: --no-send on rollback, pm-on, status, adopt or rehearse is a usage error (646.332677ms)
✔ test 6: with both a pair and --send in the pointer, --no-send and --no-proof each drop what they name and keep the rest (10576.556687ms)
✔ T55 row 189: activate --no-send drops --send, and rollback and pm-on carry it off (28781.800109ms)
▶ T57 row 200: activate --sso, its refusals before any stop, and the rollback to a Console without --sso
✔ item 1: --sso drops the preview set and the pair, puts --sso before --send and the tester port, and the plan names what it drops and carries (3731.038851ms)
✔ item 1: --sso may re-pin the live copy once; later activates carry it; --no-send still drops --send (9441.965146ms)
✔ item 1: --sso with a pair or --no-proof, or on any verb but activate, is a usage error (804.681755ms)
✔ item 2: a target without --sso, a missing or bad console.sso, and each slot problem refuse before any stop (6340.571003ms)
✔ item 3: a config whose console key the target loader refuses stops an activate or a rollback before any stop, naming the first step (4474.330517ms)
✔ item 3: a rollback from --sso to a Console without it takes the flags from the set of the activation onto the copy, past a same-copy re-pin (sage-1389) (11258.382547ms)
✔ item 3: a rollback set that is missing, fails its sums, holds no argv or already ran --sso refuses sso-rollback-flags (4811.481384ms)
✔ round 2 (webby-0028 N1): run through a symlinked repository path, a slot or a proof file inside the repository still refuses (2548.588058ms)
✔ round 2 (webby-0028 N2): a config.json that does not parse stops the run before anything is read or stopped (2148.082108ms)
✔ round 3 (sage-1556): a named instance turns --sso on with its own config, slot and Console unit, and rolls back from its own set (4427.346674ms)
✔ row 238 (#1744): on a named instance the sso-config first step names --instance NAME in both commands (1072.216503ms)
✔ T57 row 200: activate --sso, its refusals before any stop, and the rollback to a Console without --sso (51060.894572ms)
✔ R1 rehearse passes on the same schema; the scratch root holds its files and the live files stay byte-identical (3501.665564ms)
✔ R2 rehearse runs the migration checks when the schema differs; the live store stays at 3g (2615.765886ms)
✔ R3 a failing migration gives REHEARSAL_FAIL migrate and starts nothing (406.432761ms)
✔ R4 a host that does not start, does not answer status, reports another pid, launches anyway, makes a launches directory or keeps its lock each fails by name (23643.921414ms)
✔ R5 a host that ignores SIGTERM gets SIGKILL and FAIL host-stopped (4522.753011ms)
✔ R6 a Console that does not answer /healthz gives REHEARSAL_FAIL healthz (5695.642056ms)
✔ R7 a host on another copy fails copy-match (5707.109175ms)
✔ R8 a scratch port in use or reserved refuses, and the rehearsal takes no other port (564.078959ms)
✔ R9 mos-0963: a role reference left outside placeholders/, or left on an env variable, fails credential-ref-live before the host starts (739.463019ms)
✔ R10 mos-0962: a scratch business that still sets a tracker fails tracker-configured (356.527698ms)
✔ R11 mos-0961: a scratch config on the live data root or on any root but the scratch one, or a sessions.json naming a running pid, refuses by name (1415.617777ms)
✔ R12 mos-0962: a notifier config with a binding fails notify-off (346.615997ms)
✔ R13 the pre-checks: an old copy, a backup that fails its sums, a missing backup or pointer, and usage (495.179039ms)
✔ R14 a host that writes the live config, the live business file or the backup fails the end check by name (10325.286968ms)
✔ R15 a rehearsal stopped by TERM, INT or HUP fails signal and still stops the host and Console and runs the end checks (9991.220342ms)
✔ row 233: --backup last takes the newest backup the log records; a later line with backup null does not hide it, and the store line names it (3454.052038ms)
✔ row 233: --backup last with no backup in the log, or no log, exits 3 with no-backup and makes no rehearsal directory (203.934243ms)
✔ row 233: --backup last refuses a newest backup that is not an activation id, with exit 1 (127.433768ms)
▶ T56 row 190: named instances side by side on one unit directory, $BIN and set of stand-ins
✔ T-I3 each instance renders its own config, session env and Description, and nothing of another (1595.173571ms)
✔ T-I4 a Console port another installed Console takes refuses port-claimed, and a named instance keeps off the default ports (1992.011586ms)
✔ T-I6 alpha's activate and rollback reach nothing of beta's (3927.69359ms)
✔ T-I6 swapped: beta's reach nothing of alpha's (4046.222781ms)
✔ T-I7 MOSAIC_CONFIG naming alpha's config with no flag refuses instance-root-mismatch and changes nothing (707.673985ms)
✔ T-I9 another instance's bus active refuses nothing; one of this instance's does (6093.674502ms)
✔ T56 row 190: named instances side by side on one unit directory, $BIN and set of stand-ins (18364.096442ms)
ℹ tests 196
ℹ suites 0
ℹ pass 196
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 530980.947325
@@ -0,0 +1 @@
{"processes": [], "removed": "/mnt/storage/scratch/gt-t2e8hpyu", "trees": ["/home/jwoltje/.t3/worktrees/mosaic-stack/herb-row215-on212", "/home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197-r8"]}
@@ -0,0 +1 @@
{"pid": 765123, "reason": "sage-1983 stop after file10; do not start file11"}
@@ -0,0 +1,122 @@
[
{
"file": "packages/bus/tests/library.test.mjs",
"rc": 0,
"counts": {
"tests": 6,
"pass": 6,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "01-packages_bus_tests_library.test.mjs.log"
},
{
"file": "packages/bus/tests/project-runtime.test.mjs",
"rc": 0,
"counts": {
"tests": 3,
"pass": 3,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "02-packages_bus_tests_project-runtime.test.mjs.log"
},
{
"file": "packages/business/tests/business.test.mjs",
"rc": 0,
"counts": {
"tests": 18,
"pass": 18,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "03-packages_business_tests_business.test.mjs.log"
},
{
"file": "packages/business/tests/cli.test.mjs",
"rc": 0,
"counts": {
"tests": 10,
"pass": 10,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "04-packages_business_tests_cli.test.mjs.log"
},
{
"file": "packages/business/tests/instance-settings.test.mjs",
"rc": 0,
"counts": {
"tests": 12,
"pass": 12,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "05-packages_business_tests_instance-settings.test.mjs.log"
},
{
"file": "packages/business/tests/library-projects-protected.test.mjs",
"rc": 0,
"counts": {
"tests": 4,
"pass": 4,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "06-packages_business_tests_library-projects-protected.test.mjs.log"
},
{
"file": "packages/business/tests/resolve.test.mjs",
"rc": 0,
"counts": {
"tests": 11,
"pass": 11,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "07-packages_business_tests_resolve.test.mjs.log"
},
{
"file": "packages/cli/tests/launcher.test.mjs",
"rc": 0,
"counts": {
"tests": 30,
"pass": 30,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "08-packages_cli_tests_launcher.test.mjs.log"
},
{
"file": "packages/cli/tests/stack-release-external.test.mjs",
"rc": 0,
"counts": {
"tests": 25,
"pass": 25,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "09-packages_cli_tests_stack-release-external.test.mjs.log"
},
{
"file": "packages/cli/tests/stack-release.test.mjs",
"rc": 0,
"counts": {
"tests": 196,
"pass": 196,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"log": "10-packages_cli_tests_stack-release.test.mjs.log"
}
]
@@ -0,0 +1 @@
/mnt/storage/scratch/gt-t2e8hpyu
@@ -0,0 +1 @@
2026-10-21T11:56:56.177608+00:00
@@ -0,0 +1,10 @@
✔ C2-04: library mappings load without roots, remain frozen, and retain mapping refusals (9.004507ms)
✔ C2-04: an otherwise valid publish grant cannot name a library repo (1.807264ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 67.488189
@@ -0,0 +1,23 @@
✖ C2-04: library mappings load without roots, remain frozen, and retain mapping refusals (5.192384ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54.487487
✖ failing tests:
test at packages/business/tests/business.test.mjs:11:1
✖ C2-04: library mappings load without roots, remain frozen, and retain mapping refusals (5.192384ms)
Error [BusinessError]: unsupported /mnt/storage/scratch/gt-PvGtsQ/mosaic-business-4vAhxB/businesses/acme.json projects.books key: "kind"
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/business/src/errors.mjs:12:9)
at rejectUnknownKeys (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/business/src/util.mjs:17:33)
at validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/business/src/business.mjs:232:5)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/business/tests/business.test.mjs:17:18)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
exitCode: 2
}
@@ -0,0 +1,9 @@
✔ C2-04: ensureCheckouts skips a library before a real clone and leaves no books path (24.411494ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 72.211367
@@ -0,0 +1,8 @@
{
"at": "2026-10-21T08:51:07Z",
"tempRoot": "/mnt/storage/scratch/gt-PvGtsQ",
"fixtureCwdOrArgv": [],
"testProcesses": [],
"excluded": "current scan process and its ancestors",
"tempRootRemoved": true
}
@@ -0,0 +1,9 @@
✔ C2-04: business validate and resolve skip a library before code projects and credential checks (206.597157ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 260.97412
@@ -0,0 +1,9 @@
✔ C2-04: instance settings skip a library while checking a later code project (22.656666ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 79.494698
@@ -0,0 +1,9 @@
✔ C2-04: launcher workspace clones code but never the library (1241.43402ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1338.915726
@@ -0,0 +1,12 @@
✔ C2-04 M9: changed playground and hybrid entries leave business and changes.jsonl byte-identical (21.916712ms)
✔ C2-04 M9: code projects, additions, deletions and array changes are protected too (17.081275ms)
✔ C2-04 M9: canonical key order is allowed and a non-project setter still records its change (14.304068ms)
✔ C2-04 M9: a serialization hook cannot change projects after the canonical comparison (9.595281ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 116.250811
@@ -0,0 +1,28 @@
✖ C2-04 M9: changed playground and hybrid entries leave business and changes.jsonl byte-identical (21.728048ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 71.097427
✖ failing tests:
test at packages/business/tests/library-projects-protected.test.mjs:35:1
✖ C2-04 M9: changed playground and hybrid entries leave business and changes.jsonl byte-identical (21.728048ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at protectedWrite (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/business/tests/library-projects-protected.test.mjs:29:10)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/business/tests/library-projects-protected.test.mjs:37:3)
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: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: { code: 'organization-projects-protected' },
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,9 @@
✔ C2-04: record APIs refuse a library before creating a repository (3.426617ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 61.266328
@@ -0,0 +1,9 @@
✔ C2-04: a library first in projects does not hide a later project layer (6.517821ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 62.270511
@@ -0,0 +1,90 @@
[
{
"file": "business-green.log",
"tests": 2,
"pass": 2,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "business-red.log",
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
{
"file": "checkout-green.log",
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "cli-green.log",
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "instance-settings-green.log",
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "launcher-green.log",
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "m9-green.log",
"tests": 4,
"pass": 4,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "m9-red.log",
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
{
"file": "project-green.log",
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "resolve-green.log",
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
{
"file": "trackers-boot-green.log",
"tests": 2,
"pass": 2,
"fail": 0,
"cancelled": 0,
"skipped": 0
}
]
@@ -0,0 +1 @@
2026-10-21T08:48:14Z
@@ -0,0 +1 @@
/mnt/storage/scratch/gt-PvGtsQ
@@ -0,0 +1,12 @@
✔ C2-04: a library-only business with tracker.baseUrl boots without a project tracker (132.133246ms)
task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200
task.close on a missing task answered: task-not-found; it made GET /tasks/999 404
✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (261.808635ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 480.083569
@@ -0,0 +1 @@
{"processes": [], "removed": "/mnt/storage/scratch/gt-iwfcrf5s"}
@@ -0,0 +1 @@
["packages/library/tests/mirror.test.mjs", "packages/bus/tests/library.test.mjs", "packages/webui/tests/library-cli.test.mjs", "packages/bus/tests/project-runtime.test.mjs"]
@@ -0,0 +1,31 @@
✖ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (140.263767ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 244.49705
✖ failing tests:
test at packages/webui/tests/library-cli.test.mjs:10:1
✖ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (140.263767ms)
AssertionError [ERR_ASSERTION]: refused: missing library callbacks
2 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/webui/tests/library-cli.test.mjs:27: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: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,47 @@
✖ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (31.496523ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 122.065824
✖ failing tests:
test at packages/webui/tests/library-cli.test.mjs:10:1
✖ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (31.496523ms)
AssertionError [ERR_ASSERTION]: file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/webui/src/bus.mjs:1
import {appendFileSync} from 'node:fs';export class BusReadError extends Error{};export const busCaller=o=>async(verb,args)=>{appendFileSync("/mnt/storage/scratch/gt-iwfcrf5s/library-cli-1eUPUX/calls.jsonl",JSON.stringify({verb,args,cli:o.cli??"/mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/human-cli.mjs"})+'
^
SyntaxError: Invalid or unexpected token
at compileSourceTextModule (node:internal/modules/esm/utils:355:16)
at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:93:18)
at #translate (node:internal/modules/esm/loader:441:20)
at afterLoad (node:internal/modules/esm/loader:509:29)
at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12)
at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)
at afterResolve (node:internal/modules/esm/loader:614:52)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:620:12)
at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
at ModuleJob.link (node:internal/modules/esm/module_job:389:17)
Node.js v26.8.1
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/webui/tests/library-cli.test.mjs:27: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: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,9 @@
✔ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (110.620217ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 194.067409
@@ -0,0 +1,9 @@
✔ C2-04 CLI: both listener callback pairs dispatch library verbs with their own transport (107.138425ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 193.527058
@@ -0,0 +1,71 @@
✖ C2-04 M4: both verbs reject every agent before arguments or adapter access (14.387463ms)
✖ C2-04 bus: human reads validate keys and return adapter results without path leakage (8.808227ms)
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (15.107055ms)
ℹ tests 3
ℹ suites 0
ℹ pass 0
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 125.805576
✖ failing tests:
test at packages/bus/tests/library.test.mjs:28:1
✖ C2-04 M4: both verbs reject every agent before arguments or adapter access (14.387463ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
Comparison {
+ code: 'unknown-verb'
- code: 'human-required'
}
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:32:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: unknown-verb
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at #dispatch (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:3098:9)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1810:38
at Store.transaction (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/store.mjs:263:22)
at Broker.request (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1801:26)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:24:78
at async waitForActual (node:assert:615:5)
at async strict.rejects (node:assert:738:25)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:32:5)
at async Test.run (node:internal/test_runner/test:1409:7),
expected: { code: 'human-required' },
operator: 'rejects',
diff: 'simple'
}
test at packages/bus/tests/library.test.mjs:45:1
✖ C2-04 bus: human reads validate keys and return adapter results without path leakage (8.808227ms)
Error: unknown-verb
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at #dispatch (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:3098:9)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1810:38
at Store.transaction (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/store.mjs:263:22)
at Broker.request (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1801:26)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:24:78
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:49:17)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
code: 'unknown-verb'
}
test at packages/bus/tests/library.test.mjs:56:1
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (15.107055ms)
Error: unknown-verb
at Socket.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/client.mjs:42:17)
at Socket.emit (node:events:514:20)
at addChunk (node:internal/streams/readable:575:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:526:3)
at Readable.push (node:internal/streams/readable:406:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) {
code: 'unknown-verb'
}
@@ -0,0 +1,118 @@
✖ C2-04 M4: both verbs reject every agent before arguments or adapter access (16.66162ms)
✖ C2-04 bus: human reads validate keys and return adapter results without path leakage (9.880605ms)
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (17.417761ms)
✖ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (27.11473ms)
✖ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (21.990752ms)
✖ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (21.295513ms)
Interrupted while running:
⚠ packages/bus/tests/library.test.mjs (packages/bus/tests/library.test.mjs:1:1)
✖ packages/bus/tests/library.test.mjs (34335.85978ms)
ℹ tests 7
ℹ suites 0
ℹ pass 0
ℹ fail 6
ℹ cancelled 1
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 34341.308382
✖ failing tests:
test at packages/bus/tests/library.test.mjs:28:1
✖ C2-04 M4: both verbs reject every agent before arguments or adapter access (16.66162ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
Comparison {
+ code: 'unknown-verb'
- code: 'human-required'
}
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:32:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: unknown-verb
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at #dispatch (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:3098:9)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1810:38
at Store.transaction (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/store.mjs:263:22)
at Broker.request (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1801:26)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:24:78
at async waitForActual (node:assert:615:5)
at async strict.rejects (node:assert:738:25)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:32:5)
at async Test.run (node:internal/test_runner/test:1409:7),
expected: { code: 'human-required' },
operator: 'rejects',
diff: 'simple'
}
test at packages/bus/tests/library.test.mjs:45:1
✖ C2-04 bus: human reads validate keys and return adapter results without path leakage (9.880605ms)
Error: unknown-verb
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at #dispatch (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:3098:9)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1810:38
at Store.transaction (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/store.mjs:263:22)
at Broker.request (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:1801:26)
at file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:24:78
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:49:17)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
code: 'unknown-verb'
}
test at packages/bus/tests/library.test.mjs:56:1
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (17.417761ms)
Error: unknown-verb
at Socket.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/client.mjs:42:17)
at Socket.emit (node:events:514:20)
at addChunk (node:internal/streams/readable:575:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:526:3)
at Readable.push (node:internal/streams/readable:406:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) {
code: 'unknown-verb'
}
test at packages/bus/tests/library.test.mjs:67:1
✖ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (27.11473ms)
TypeError: library.mirrorPath is not a function
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:74:26)
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)
test at packages/bus/tests/library.test.mjs:81:1
✖ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (21.990752ms)
AssertionError [ERR_ASSERTION]: Missing expected rejection.
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:87:3)
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: undefined,
expected: { code: 'library-git' },
operator: 'rejects',
diff: 'simple'
}
test at packages/bus/tests/library.test.mjs:92:1
✖ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (21.295513ms)
Error: unknown-verb
at Socket.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/client.mjs:42:17)
at Socket.emit (node:events:514:20)
at addChunk (node:internal/streams/readable:575:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:526:3)
at Readable.push (node:internal/streams/readable:406:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) {
code: 'unknown-verb'
}
test at packages/bus/tests/library.test.mjs:1:1
✖ packages/bus/tests/library.test.mjs (34335.85978ms)
'Promise resolution is still pending but the event loop has already resolved'
@@ -0,0 +1,126 @@
✖ C2-04 M4: both verbs reject every agent before arguments or adapter access (24.976387ms)
✖ C2-04 bus: human reads validate keys and return adapter results without path leakage (7.097425ms)
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (6.894431ms)
✖ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (34.318499ms)
✖ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (21.188728ms)
✖ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (11.406264ms)
ℹ tests 6
ℹ suites 0
ℹ pass 0
ℹ fail 6
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 197.495962
✖ failing tests:
test at packages/bus/tests/library.test.mjs:28:1
✖ C2-04 M4: both verbs reject every agent before arguments or adapter access (24.976387ms)
Error: invalid-business
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at new Broker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:322:9)
at world (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:20:18)
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:30:13)
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) {
code: 'invalid-business'
}
test at packages/bus/tests/library.test.mjs:45:1
✖ C2-04 bus: human reads validate keys and return adapter results without path leakage (7.097425ms)
Error: invalid-business
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at new Broker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:322:9)
at world (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:20:18)
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:47:13)
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) {
code: 'invalid-business'
}
test at packages/bus/tests/library.test.mjs:56:1
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (6.894431ms)
Error: invalid-business
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at new Broker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:322:9)
at world (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:20:18)
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:57:13)
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 Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
code: 'invalid-business'
}
test at packages/bus/tests/library.test.mjs:67:1
✖ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (34.318499ms)
Error: credential-format
at deny (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/credentials.mjs:10:9)
at #format (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/credentials.mjs:232:28)
at #read (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/credentials.mjs:222:37)
at new Credentials (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/credentials.mjs:145:35)
at startBroker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/runtime.mjs:108:19)
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:72:25)
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) {
code: 'credential-format'
}
test at packages/bus/tests/library.test.mjs:81:1
✖ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (21.188728ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
Comparison {
+ code: 'invalid-business'
- code: 'library-git'
}
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:86:3)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: invalid-business
at fail (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:145:9)
at new Broker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/broker.mjs:322:9)
at startBroker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/runtime.mjs:126:20)
at async waitForActual (node:assert:615:5)
at async strict.rejects (node:assert:738:25)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:86:3)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7),
expected: { code: 'library-git' },
operator: 'rejects',
diff: 'simple'
}
test at packages/bus/tests/library.test.mjs:91:1
✖ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (11.406264ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/scratch/gt-iwfcrf5s/library-m65UFF/data'
at lstatSync (node:fs:2138:25)
at new Store (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/store.mjs:137:22)
at startBroker (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/src/runtime.mjs:99:13)
at TestContext.<anonymous> (file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/bus/tests/library.test.mjs:94:25)
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 Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/scratch/gt-iwfcrf5s/library-m65UFF/data'
}
@@ -0,0 +1,14 @@
✔ C2-04 M4: both verbs reject every agent before arguments or adapter access (28.309128ms)
✔ C2-04 bus: human reads validate keys and return adapter results without path leakage (11.198339ms)
✔ C2-04 socket: each async library verb resolves its result rather than serializing {} (14.717312ms)
✔ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (42.066656ms)
✔ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (23.773967ms)
✔ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (89.925539ms)
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 301.460553
@@ -0,0 +1,63 @@
✔ C2-04 M4: both verbs reject every agent before arguments or adapter access (27.706148ms)
✔ C2-04 bus: human reads validate keys and return adapter results without path leakage (10.128912ms)
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (14.255977ms)
✖ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (20.969978ms)
✔ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (19.148653ms)
✖ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (10.834076ms)
ℹ tests 6
ℹ suites 0
ℹ pass 3
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 192.486443
✖ failing tests:
test at packages/bus/tests/library.test.mjs:56:1
✖ C2-04 socket: each async library verb resolves its result rather than serializing {} (14.255977ms)
Error: unauthenticated
at Socket.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/client.mjs:42:17)
at Socket.emit (node:events:514:20)
at addChunk (node:internal/streams/readable:575:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:526:3)
at Readable.push (node:internal/streams/readable:406:5)
at Pipe.onStreamRead (node:internal/stream_base_commons:189:23) {
code: 'unauthenticated'
}
test at packages/bus/tests/library.test.mjs:67:1
✖ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (20.969978ms)
Error: credential-format
at deny (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/credentials.mjs:10:9)
at #format (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/credentials.mjs:232:28)
at #read (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/credentials.mjs:222:37)
at new Credentials (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/credentials.mjs:145:35)
at startBroker (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/runtime.mjs:109:19)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/tests/library.test.mjs:72:25)
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) {
code: 'credential-format'
}
test at packages/bus/tests/library.test.mjs:91:1
✖ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (10.834076ms)
Error: ENOENT: no such file or directory, lstat '/mnt/storage/scratch/gt-iwfcrf5s/library-rRi0EP/data'
at lstatSync (node:fs:2138:25)
at new Store (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/store.mjs:137:22)
at startBroker (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/src/runtime.mjs:100:13)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/bus/tests/library.test.mjs:94:25)
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 process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
errno: -2,
code: 'ENOENT',
syscall: 'lstat',
path: '/mnt/storage/scratch/gt-iwfcrf5s/library-rRi0EP/data'
}
@@ -0,0 +1,14 @@
✔ C2-04 M4: both verbs reject every agent before arguments or adapter access (29.476748ms)
✔ C2-04 bus: human reads validate keys and return adapter results without path leakage (11.765761ms)
✔ C2-04 socket: each async library verb resolves its result rather than serializing {} (15.523467ms)
✔ C2-04 runtime: protectedRoots skips library but retains real code-root credential protection (41.028545ms)
✔ C2-04 runtime: unsafe existing mirror refuses boot and never publishes a socket (20.174444ms)
✔ C2-04 runtime: real library adapters reach the socket and lock waits leave other bus reads responsive (89.276012ms)
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 314.116154
@@ -0,0 +1,35 @@
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/mnt/storage/scratch/gt-iwfcrf5s/base/packages/library/src/mirror.mjs' imported from /mnt/storage/scratch/gt-iwfcrf5s/base/packages/library/tests/mirror.test.mjs
at finalizeResolution (node:internal/modules/esm/resolve:272:11)
at moduleResolve (node:internal/modules/esm/resolve:879:10)
at defaultResolve (node:internal/modules/esm/resolve:1006:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
at #resolve (node:internal/modules/esm/loader:690:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
at ModuleJob.link (node:internal/modules/esm/module_job:389:17) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/library/src/mirror.mjs'
}
Node.js v26.8.1
✖ packages/library/tests/mirror.test.mjs (35.196517ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 41.636181
✖ failing tests:
test at packages/library/tests/mirror.test.mjs:1:1
✖ packages/library/tests/mirror.test.mjs (35.196517ms)
'test failed'
@@ -0,0 +1,35 @@
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/mnt/storage/scratch/gt-iwfcrf5s/base/packages/library/src/mirror.mjs' imported from /mnt/storage/scratch/gt-iwfcrf5s/base/packages/library/tests/mirror.test.mjs
at finalizeResolution (node:internal/modules/esm/resolve:272:11)
at moduleResolve (node:internal/modules/esm/resolve:879:10)
at defaultResolve (node:internal/modules/esm/resolve:1006:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
at #resolve (node:internal/modules/esm/loader:690:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
at ModuleJob.link (node:internal/modules/esm/module_job:389:17) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///mnt/storage/scratch/gt-iwfcrf5s/base/packages/library/src/mirror.mjs'
}
Node.js v26.8.1
✖ packages/library/tests/mirror.test.mjs (29.912154ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 35.532092
✖ failing tests:
test at packages/library/tests/mirror.test.mjs:1:1
✖ packages/library/tests/mirror.test.mjs (29.912154ms)
'test failed'
@@ -0,0 +1,24 @@
✔ C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization (38.226069ms)
✔ C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths (28.230282ms)
✔ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (102.534821ms)
✔ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (13.3233ms)
✔ C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock (21.415494ms)
✔ C2-04 state: missing state is empty only while the served ref is absent (35.194627ms)
✔ C2-04 state: valid problems and sync metadata return no content and change no state bytes (37.132264ms)
✔ C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed (99.673047ms)
✔ C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement (28.715976ms)
✔ C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text (71.638861ms)
✔ C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies (78.457144ms)
✔ C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes (228.356974ms)
✔ C2-04 lock: same-process waits, timeout code, callback expiry and throw release (5027.769608ms)
✔ C2-04 lock: owner death releases the kernel lock without replacing its file (52.875238ms)
✔ C2-04 lock: a missing flock binary becomes a handled refusal (49.021765ms)
✔ C2-04 lock: an unawaited scoped read drains before the lock is released (20.779739ms)
ℹ tests 16
ℹ suites 0
ℹ pass 16
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5980.465131
@@ -0,0 +1,30 @@
✔ C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization (42.294098ms)
✔ C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths (29.430204ms)
✖ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (15050.820834ms)
✔ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (13.507402ms)
✔ C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock (19.93202ms)
✔ C2-04 state: missing state is empty only while the served ref is absent (33.088494ms)
✔ C2-04 state: valid problems and sync metadata return no content and change no state bytes (34.798253ms)
✔ C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed (96.144919ms)
✔ C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement (28.529103ms)
✔ C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text (71.331837ms)
✔ C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies (80.081788ms)
✔ C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes (232.208946ms)
✔ C2-04 lock: same-process waits, timeout code, callback expiry and throw release (5028.099129ms)
✔ C2-04 lock: owner death releases the kernel lock without replacing its file (55.474555ms)
✔ C2-04 lock: a missing flock binary becomes a handled refusal (51.152959ms)
✔ C2-04 lock: an unawaited scoped read drains before the lock is released (21.006119ms)
ℹ tests 16
ℹ suites 0
ℹ pass 15
ℹ fail 0
ℹ cancelled 1
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 20945.326932
✖ failing tests:
test at packages/library/tests/mirror.test.mjs:53:1
✖ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (15050.820834ms)
'test timed out after 15000ms'
@@ -0,0 +1,23 @@
✔ C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization (37.162857ms)
✔ C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths (28.538429ms)
✔ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (10.987669ms)
✔ C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock (20.080027ms)
✔ C2-04 state: missing state is empty only while the served ref is absent (33.757482ms)
✔ C2-04 state: valid problems and sync metadata return no content and change no state bytes (35.501927ms)
✔ C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed (96.276094ms)
✔ C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement (28.464199ms)
✔ C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text (72.087911ms)
✔ C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies (78.120774ms)
✔ C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes (230.905584ms)
✔ C2-04 lock: same-process waits, timeout code, callback expiry and throw release (5029.743303ms)
✔ C2-04 lock: owner death releases the kernel lock without replacing its file (51.842651ms)
✔ C2-04 lock: a missing flock binary becomes a handled refusal (48.619585ms)
✔ C2-04 lock: an unawaited scoped read drains before the lock is released (19.747384ms)
ℹ tests 15
ℹ suites 0
ℹ pass 15
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5872.380072
@@ -0,0 +1,51 @@
✔ C2-04 mirror: exact default path, bare without checkout or remote, and idempotent initialization (35.114121ms)
✔ C2-04 mirror: simultaneous creators publish one intact mirror and leave no staging paths (28.344028ms)
✖ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (11.29287ms)
✔ C2-04 mirror: existing metadata accepts only bare Git plus state and the precreated lock (21.069667ms)
✔ C2-04 state: missing state is empty only while the served ref is absent (33.576129ms)
✔ C2-04 state: valid problems and sync metadata return no content and change no state bytes (36.355449ms)
✔ C2-04 state: strict keys, types, times, branch, commit and fixed R messages fail closed (98.678405ms)
✔ C2-04 state: symlink, hardlink, non-private state and missing lock refuse without replacement (28.476513ms)
✔ C2-04 file: served commit, not HEAD or a caller commit, owns the exact returned text (72.613881ms)
✔ C2-04 file: R1, R2 and R7 exclusions and never-synced mirrors cannot return file bodies (78.072657ms)
✔ C2-04 lock: a first-sync and fast-forward reader wait for a consistent pair across processes (230.109966ms)
✔ C2-04 lock: same-process waits, timeout code, callback expiry and throw release (5027.239374ms)
✔ C2-04 lock: owner death releases the kernel lock without replacing its file (53.970224ms)
✔ C2-04 lock: a missing flock binary becomes a handled refusal (45.088532ms)
✔ C2-04 lock: an unawaited scoped read drains before the lock is released (19.793614ms)
ℹ tests 15
ℹ suites 0
ℹ pass 14
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5869.613351
✖ failing tests:
test at packages/library/tests/mirror.test.mjs:53:1
✖ C2-04 mirror: preserve occupied directories and reject symlink ancestors and code overlaps (11.29287ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /overlap/. Input:
'Error: project-path: unsafe mirror path'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/tests/mirror.test.mjs:64:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: project-path: unsafe mirror path
at Module.mirrorPath (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:40:25)
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/tests/mirror.test.mjs:64:27
at getActual (node:assert:580:5)
at strict.throws (node:assert:728:24)
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/tests/mirror.test.mjs:64: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),
expected: /overlap/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,20 @@
✖ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (56.393634ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 102.30317
✖ failing tests:
test at packages/library/tests/mirror.test.mjs:53:1
✖ C2-04 mirror: skipped publication accepts both coreutils statuses without removing the winner (56.393634ms)
Error: {"code":1,"stderr":"file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:18\nconst fail = (code, message = code) => { throw new LibraryError(code, message); };\n ^\n\nLibraryError: library-git: expected a private owned bare mirror\n at fail (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:18:48)\n at ownedRoot (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:49:13)\n at inspectMirrorUnchecked (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:57:3)\n at inspectMirror (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:94:15)\n at ensureMirror (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/src/mirror.mjs:128:11)\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n at async file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/[eval1]:7:149 {\n code: 'library-git'\n}\n\nNode.js v26.8.1\n"}
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/tests/mirror.test.mjs:65:108
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/herb-row197/packages/library/tests/mirror.test.mjs:65:22)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
@@ -0,0 +1,11 @@
✔ Broker keeps the sandbox client import closure: only the listed imports, no lazy project import (1.794155ms)
✔ without a trusted project adapter all three async paths refuse project-not-configured (39.865453ms)
✔ real startBroker injects the project adapter for socket reads and bound-author record writes (65.643553ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 197.45826
@@ -0,0 +1,11 @@
✔ Broker keeps the sandbox client import closure: only the listed imports, no lazy project import (1.418597ms)
✔ without a trusted project adapter all three async paths refuse project-not-configured (34.510685ms)
✔ real startBroker injects the project adapter for socket reads and bound-author record writes (59.944824ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 183.05447
@@ -0,0 +1,51 @@
{
"mirror.test-final-r2.log": {
"tests": 16,
"pass": 16,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"library.test-final.log": {
"tests": 6,
"pass": 6,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"library-cli.test-final.log": {
"tests": 1,
"pass": 1,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"project-runtime.test-final.log": {
"tests": 3,
"pass": 3,
"fail": 0,
"cancelled": 0,
"skipped": 0
},
"library.test-baseline-focused.log": {
"tests": 3,
"pass": 0,
"fail": 3,
"cancelled": 0,
"skipped": 0
},
"library-cli.test-baseline-r2.log": {
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
},
"mirror.test-baseline-r2.log": {
"tests": 1,
"pass": 0,
"fail": 1,
"cancelled": 0,
"skipped": 0
}
}
@@ -0,0 +1 @@
/mnt/storage/scratch/gt-iwfcrf5s
@@ -0,0 +1,8 @@
{
"base": "a2c72ac95",
"landing": "fefd54e13",
"overlaps": [
"BUILD-LOG.md",
"packages/cli/tests/stack-release.test.mjs"
]
}
+189
View File
@@ -0,0 +1,189 @@
{
"oldBase": "f8276c5eb3ecb0aad353ee65e3dd8f7faac5dd87",
"oldSource": "e333af4dc7af3a9d3789c8c0db26a0df8bdddfee",
"newBase": "a3652371338e60187d4e95cee4a0f30f3c8a8f06",
"paths": [
{
"path": "BUILD-LOG.md",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/bus/src/broker.mjs",
"upstreamChanged": true,
"matchesPriorBlob": false,
"changeLinesIdentical": true
},
{
"path": "packages/bus/src/runtime.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/bus/src/server.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/bus/tests/library.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/src/business.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/src/cli.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/src/instance-settings.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/src/organization.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/src/resolve.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/tests/business.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/tests/cli.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/tests/instance-settings.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/tests/library-projects-protected.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/business/tests/resolve.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/cli/src/config.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/cli/src/launcher.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/cli/tests/launcher.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/cli/tests/stack-release-fixture.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/cli/tests/stack-release.test.mjs",
"upstreamChanged": true,
"matchesPriorBlob": false,
"changeLinesIdentical": true
},
{
"path": "packages/cli/tests/trackers-boot.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/library/src/index.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/library/src/mirror.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/library/tests/mirror.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/project/src/paths.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/project/tests/project.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/seat/src/checkout.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/seat/tests/checkout.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/webui/src/cli.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
},
{
"path": "packages/webui/tests/library-cli.test.mjs",
"upstreamChanged": false,
"matchesPriorBlob": true,
"changeLinesIdentical": true
}
],
"newSource": "a6101e21c596e2972ab5a7e3087736075899505e",
"stablePatchId": "d191e7e9586cd3b7c18310daacdf0c1fdbc8be01"
}
@@ -0,0 +1,80 @@
packages/bus/tests/library.test.mjs
packages/bus/tests/project-runtime.test.mjs
packages/business/tests/business.test.mjs
packages/business/tests/cli.test.mjs
packages/business/tests/instance-settings.test.mjs
packages/business/tests/library-projects-protected.test.mjs
packages/business/tests/resolve.test.mjs
packages/cli/tests/launcher.test.mjs
packages/cli/tests/stack-release-external.test.mjs
packages/cli/tests/stack-release.test.mjs
packages/cli/tests/stack-upgrade.test.mjs
packages/cli/tests/trackers-boot.test.mjs
packages/library/tests/mirror.test.mjs
packages/project/tests/project.test.mjs
packages/seat/tests/checkout.test.mjs
packages/webui/tests/library-cli.test.mjs
packages/bus/tests/agents.test.mjs
packages/bus/tests/approval-binding.test.mjs
packages/bus/tests/broker.test.mjs
packages/bus/tests/business.test.mjs
packages/bus/tests/channel-proof.test.mjs
packages/bus/tests/chief-of-staff.test.mjs
packages/bus/tests/comment-cursors.test.mjs
packages/bus/tests/creative.test.mjs
packages/bus/tests/credential-claim.test.mjs
packages/bus/tests/credential-status.test.mjs
packages/bus/tests/credential-store.test.mjs
packages/bus/tests/credential-verbs.test.mjs
packages/bus/tests/credentials.test.mjs
packages/bus/tests/csuite-messaging.test.mjs
packages/bus/tests/daily-quota-runtime.test.mjs
packages/bus/tests/decision-close.test.mjs
packages/bus/tests/decision-route.test.mjs
packages/bus/tests/decisions.test.mjs
packages/bus/tests/end-launch.test.mjs
packages/bus/tests/grant-enroll.test.mjs
packages/bus/tests/grant-executor.test.mjs
packages/bus/tests/grant-host.test.mjs
packages/bus/tests/grant-migration.test.mjs
packages/bus/tests/hard-waits.test.mjs
packages/bus/tests/host-run.test.mjs
packages/bus/tests/human.test.mjs
packages/bus/tests/instances-verb.test.mjs
packages/bus/tests/land.test.mjs
packages/bus/tests/launch-notice.test.mjs
packages/bus/tests/limit-stop.test.mjs
packages/bus/tests/limits-runtime.test.mjs
packages/bus/tests/limits.test.mjs
packages/bus/tests/message-citation.test.mjs
packages/bus/tests/messages.test.mjs
packages/bus/tests/messaging.test.mjs
packages/bus/tests/names.test.mjs
packages/bus/tests/plugins-verb.test.mjs
packages/bus/tests/process.test.mjs
packages/bus/tests/project-await.test.mjs
packages/bus/tests/project-files.test.mjs
packages/bus/tests/publish.test.mjs
packages/bus/tests/replies.test.mjs
packages/bus/tests/requirements.test.mjs
packages/bus/tests/review.test.mjs
packages/bus/tests/routes-verb.test.mjs
packages/bus/tests/single-use.test.mjs
packages/bus/tests/spec-review.test.mjs
packages/bus/tests/spec-socket.test.mjs
packages/bus/tests/spec.test.mjs
packages/bus/tests/staff-request.test.mjs
packages/bus/tests/state.test.mjs
packages/bus/tests/task-page.test.mjs
packages/bus/tests/task-read.test.mjs
packages/bus/tests/tasks.test.mjs
packages/bus/tests/tester-threads.test.mjs
packages/bus/tests/tester-writes.test.mjs
packages/bus/tests/tester.test.mjs
packages/bus/tests/thread-migration.test.mjs
packages/bus/tests/threads.test.mjs
packages/bus/tests/transport.test.mjs
packages/bus/tests/usage-broker.test.mjs
packages/bus/tests/usage-runtime.test.mjs
packages/bus/tests/web.test.mjs
packages/bus/tests/workflow-roles.test.mjs
@@ -0,0 +1,86 @@
diff --git a/packages/bus/src/broker.mjs b/packages/bus/src/broker.mjs
index 135a1699a..f0eb4ae65 100644
--- a/packages/bus/src/broker.mjs
+++ b/packages/bus/src/broker.mjs
@@ -88,6 +88,7 @@ export const TASK_PAGE_MAX = 200;
// Row 148 (#1642): the human-only verbs behind the Console's document editor. The save is the one
// request allowed past the ordinary 64 KiB envelope (server.mjs and client.mjs), up to its content cap.
export const PROJECT_FILE_VERBS = Object.freeze(['project.files', 'project.file', 'project.file.save']);
+export const LIBRARY_VERBS = Object.freeze(['library.file', 'library.status']);
export const PROJECT_SAVE_VERB = 'project.file.save';
export const PROJECT_SAVE_REQUEST_MAX = 1024 * 1024 + 64 * 1024;
export const WEB_VERBS =Object.freeze(['web.search', 'web.fetch']);
@@ -279,6 +280,7 @@ export class Broker {
#organization;
#credentialVerbs;
#project;
+ #library;
#usage;
#limits;
#harnessOn;
@@ -294,7 +296,7 @@ export class Broker {
#stops = new Map();
#clock = 0;
// credentialStatus is the runtime's metadata-only Credentials.status(); the broker never gets use().
- constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null }) {
+ constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, library = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null }) {
if (!object(businesses) || !Object.keys(businesses).length) fail('invalid-business');
this.#businesses = structuredClone(businesses);
this.#store = store;
@@ -306,6 +308,7 @@ export class Broker {
this.#hostGrant = hostGrant;
this.#hostKeys = hostKeys;
this.#project = project;
+ this.#library = library;
this.#usage = usage;
this.#limits = limits;
// Row 65: harnessOn(business, name) is true when the organization file's plugins.harness switches
@@ -2192,6 +2195,8 @@ export class Broker {
return this.#requestProject(s, { verb: request.verb, args: structuredClone(args) });
if (PROJECT_FILE_VERBS.includes(request.verb))
return this.#requestProjectFile(s, { verb: request.verb, args: structuredClone(args) });
+ if (LIBRARY_VERBS.includes(request.verb))
+ return this.#requestLibrary(s, { verb: request.verb, args: structuredClone(args) });
if (CREDENTIAL_VERBS.includes(request.verb)) return this.#requestCredential(s, request.verb, structuredClone(args));
if (["limits", "limits.set", "organization.set", "organization.revision", "plugins", "plugins.set", "instances", "instances.set", "routes", "routes.set"].includes(request.verb)) {
// Row 123 (#1617, REQ-COS-1, REQ-LIMIT-1): organization.set is limits.set for a role whose
@@ -2441,6 +2446,26 @@ export class Broker {
throw this.#refused(s, ProjectFailure && e instanceof ProjectFailure ? new BusError(e.code) : e, request);
}
}
+ // C2-04 M4: only a verified human reads library text or status. Host Git
+ // functions stay injected, preserving this module's sandbox import closure.
+ async #requestLibrary(s, request) {
+ const { verb, args } = request;
+ try {
+ this.#human(s);
+ const fields = verb === 'library.file' ? ['project', 'path'] : ['project'];
+ keys(args, fields, fields);
+ if (!this.#library?.readFile || !this.#library?.status) fail('project-not-configured');
+ const context = { business: this.#business(s.business), project: args.project };
+ const result = verb === 'library.file'
+ ? await this.#library.readFile({ ...context, path: args.path })
+ : await this.#library.status(context);
+ this.#secretCheck(result);
+ return result;
+ } catch (error) {
+ const Failure = this.#library?.LibraryError;
+ throw this.#refused(s, Failure && error instanceof Failure ? new BusError(error.code) : error, request);
+ }
+ }
// The eight task verbs reach the trusted S3 handler here. It runs outside any transaction, gets the
// cap to authorize and record through authorize() and recordTask(); refusals are recorded as in request().
async requestTask(cap, request, handler) {
diff --git a/packages/cli/tests/stack-release.test.mjs b/packages/cli/tests/stack-release.test.mjs
index 7398247ce..0e316fd74 100644
--- a/packages/cli/tests/stack-release.test.mjs
+++ b/packages/cli/tests/stack-release.test.mjs
@@ -1779,7 +1779,7 @@ export class Store {
const RCOPIES=join(base,'rcopies');mkdirSync(RCOPIES);
for(const [id,v,froms] of [[B,'3g',['3f']],[C,'3h',['3f','3g']],[A,'3g',['3f']],[C1,'3g',['3f']]]){
const d=join(RCOPIES,id);
- for(const p of ['scripts/mosaic','scripts/mosaic-config.mjs','packages/cli/src/config.mjs','packages/cli/src/errors.mjs','packages/bus/src/business.mjs','packages/plugins/src/kinds.mjs','packages/business/src','roles'])cpSync(join(repo,p),join(d,p),{recursive:true});
+ for(const p of ['scripts/mosaic','scripts/mosaic-config.mjs','packages/cli/src/config.mjs','packages/cli/src/errors.mjs','packages/bus/src/business.mjs','packages/plugins/src/kinds.mjs','packages/business/src','packages/library/src','roles'])cpSync(join(repo,p),join(d,p),{recursive:true});
if(id===C1)writeFileSync(join(d,'scripts','mosaic'),`#!/usr/bin/env bash\nexec node "${join(RCOPIES,B,'packages','cli','src','cli.mjs')}" "$@"\n`,{mode:0o755});
for(const [f,text] of [['packages/cli/src/cli.mjs',id===A?'// fixture host from before row 163\nprocess.exit(1);\n':script(rHost)],['packages/webui/src/cli.mjs',script(rConsole)],['packages/bus/src/store.mjs',rStore(v,froms)],['packages/bus/src/migrate-schema.mjs',script(rMigrate)],['packages/bus/src/broker.mjs','export class BusError extends Error { constructor(code) { super(code); this.code = code; } }\n']]){mkdirSync(dirname(join(d,f)),{recursive:true});writeFileSync(join(d,f),text)}
execFileSync('chmod',['-R','a-w',d]);
@@ -0,0 +1,10 @@
1: e333af4dc ! 1: a6101e21c feat: add human-only library projects and served reads
@@ Metadata
Author: Herb <[email protected]>
## Commit message ##
- feat: add human-only library projects and served reads
+ feat: carry reviewed C2-04 library entry onto row 197 landing
## BUILD-LOG.md ##
@@ BUILD-LOG.md: Before: managed project/thread verbs had no Console conversation area. After: fi
File diff suppressed because it is too large Load Diff
+707
View File
@@ -0,0 +1,707 @@
# Row 194 design: agent lifecycle from the Console and the CLI, with a per-agent veto
Brief: `docs/plans/2026-10-20_agent-lifecycle.md` (blob a907109409ac). Gitea #1692. MVP T1 item 5 (mos-0974,
mos-0977, DECISIONS Q176). Owner Honker, reviewer Webby, gate owner Sage. Base: refactor at 53bb59462.
Branch `honker/row194`.
Related designs read: row 190 `agents/darkwing/work/queue-190/DESIGN.md` (r2, after mos-0986), row 191
`agents/rocko/work/queue-191/DESIGN-r2.md`. Row 193 is still in design. All line numbers below are at
53bb59462.
## Summary
- One verb family on the CLI, `mosaic business instance <verb> BUSINESS INSTANCE`, and one Console section.
Create and delete change the business file. Launch, terminate and regenerate act on runs.
- Every human verb goes through the bus with the human proof. Launch, regenerate and delete are requests the
launcher takes once and carries out under `serialize`. Launches go through the one `start()`, so
`--no-launch`, the limits, `launch.revoke` and row 191's switch all apply.
- The chief hires (adds and lists an instance) and fires (ends its runs and delists it). It never deletes.
`role.hire` and `role.fire` are new hard waits, so Jason resolves each one (Mos, mos-1017). The routed
form waits on a PRD 1.7 draft in this packet.
- A hire allowlist in code: the chief can hire only a definition whose every action is on `HIRE_ALLOWED`
(message, record, review request, assigned-task updates, and two actions that matter after row 199).
Launch, end, organization, naming, hire, verdict, merge and every later action stay out.
- The per-agent forbid is two lists in the business file that only the human writes: `launch.keep`
(instances the chief can't fire, replace or end) and `launch.noHire` (definitions the chief can't hire).
- The broker reloads a business from its file after each committed write. Create, delete, hire, fire and
veto changes then count without a bus restart. Hand edits still need one, except `bus.tester`.
- No bus schema change and no migration. Named for review: two new vocabulary actions (hard waits), the
role-file `hireable` key, `"hireable": []` in `roles/chief-of-staff.json`, and three business-file keys.
## What exists today (checked at 53bb59462)
| # | Fact | Where |
|---|---|---|
| E1 | Instances are keys of the business file's `roles`. `launch.instances` lists those the chief may launch. The list must be non-empty | `business.mjs:121-132` |
| E2 | The human adds an instance and changes its definition, vars and listing. No delete. Credentialed definitions are refused | `instance-settings.mjs:1-5, 22-31, 75-156` |
| E3 | The broker copies the businesses once at start and refuses a role it doesn't know | `broker.mjs:208, 446` |
| E4 | The chief launches over `launch.sock`. `fromSession` checks identity, then `check()` (which reloads the file), then `authorizeLaunch`, then `start()` | `launcher.mjs:232-327, 624-668` |
| E5 | The chief's `launch` tool takes an enum of the instances listed when its bundle was built | `harness/src/tools.mjs:116-119` |
| E6 | The chief ends a run with `role.revoke` plus a reason (`role.end`). Ending then launching again is already a replace | `broker.mjs:1601-1628`, `tools.mjs:167` |
| E7 | `mosaic stop RUN` is a same-uid SIGTERM. It has no bus call and no human proof | `cli.mjs:447-454`, `seat/src/session.mjs:249-270` |
| E8 | The Console stops only the assistant (`assistant.stop`) and writes instances through `instances.set` | `serve.mjs:282-289, 315-363, 535` |
| E9 | The launcher already starts runs for the human on its 2 s tick: threads with pending messages, and merge holders | `launcher.mjs:133, 487-597` |
| E10 | `launch.revoke` and `launch.restore` are human-only. A revoked business refuses `role.launch` | `broker.mjs:770-774, 2087-2095` |
| E11 | Only the assistant is END_PROTECTED. Coder, reviewer, pm and cto all declare gitea and vikunja credentials | `vocabulary.mjs:135`, `roles/*.json` |
The chief's veto today comes from the human-only membership in E2. The chief can only launch what the human
listed. It has no way to create an instance, and the human has no way to stop the chief ending a given
instance's runs.
## D1. The verbs
All of them are human verbs. Each is a broker verb behind `#human(s)` and then the D4 hook. Each logs its
actor. The CLI usage joins `INSTANCES_USAGE` (`cli/src/instances.mjs:9`).
| Verb | CLI | Bus verb | Effect |
|---|---|---|---|
| V1 create | `business instance add B I --definition DEF [--vars FILE] [--launch] --revision REV` | `instances.set` op `add` | as today, with no restart (D3) |
| V2 launch | `business instance launch B I [--wait SECONDS]` | `instance.launch` | one run, through the launcher |
| V3 terminate | `business instance terminate B I [--reason TEXT]` | `instance.end` | ends every run of I and cancels I's open requests |
| V4 regenerate | `business instance regenerate B I [--wait SECONDS]` | `instance.regenerate` | V3, then V2 once the run has exited |
| V5 delete | `business instance delete B I --revision REV [--wait SECONDS]` | `instances.set` op `delete` | a request. The launcher removes I from the business file and renames its workspace |
| V6 veto | `business veto B (keep\|unkeep) I` and `business veto B (nohire\|hire) DEF`, each `--revision REV` | `veto.set` | edits `launch.keep` and `launch.noHire` |
`mosaic business instances B` (read) gains, per instance: `running` (run ids from the bus claims), `kept`
and `retired`. The top level gains `noHire`. `mosaic stop RUN` stays as it is. It is the same-uid operator
tool, and V3 is the human verb.
### D1.1 Requests (V2, V4, V5)
The broker never starts a process or touches a workspace. Launch, regenerate and delete are requests the
launcher carries out, as E9 already does for the human's threads. A request has three states, open, taken
and closed, all recorded as `action.allowed` events with reserved action names:
| Event | Written by | Body |
|---|---|---|
| `lifecycle.launch`, `lifecycle.regenerate`, `lifecycle.delete` | the broker, at the verb | `{ request, via }`, subject I, actor the human or `tester`. A `human.input` event goes with it, as `launch.revoke` does |
| `lifecycle.taken` | host op `lifecycleTake` | `{ request }` |
| `lifecycle.done` | host op `lifecycleDone` | `{ request, run? , refusal? }` |
| `lifecycle.cancelled` | V3, for each open request of I | `{ request, by }` |
1. The launcher's new `lifecyclePass` runs on the same tick as `threadPass` and `landPass`. It lists open
requests with host op `lifecycleRequests`.
2. For each one, under `serialize`, it calls `lifecycleTake(request)`. That op runs in one store
transaction. It re-checks, in order: the request is still open (not taken, cancelled or closed), it is
less than 60 s old (else it closes as `request-expired`), the business isn't revoked (`launch_state`,
`launch-revoked`), I isn't retired, row 191's switch is off (`processing-halted`) for launch and
regenerate, and for a tester request `bus.tester` is true in the file (D4.1). Any failure closes the
request with that code. Otherwise it writes `lifecycle.taken` and answers go.
3. Only after go does the pass act: `check(I, { human: true })` then `start()` for a launch, the V5 steps
for a delete.
4. The pass then calls `lifecycleDone`. A taken request never runs again, whatever happens after the take.
If `lifecycleDone` throws, the request stays taken with no done event. The CLI reports that as
`outcome-unknown` with the request id, and the launcher log names the run.
5. The CLI polls a read verb, `instance.request`, until the request closes or `--wait` runs out (default
30 s). A timeout exits 3 with the request id. An untaken request can still be taken within its 60 s.
So the human's `launch.revoke` stops every untaken request, not only new ones (B4), and a bus restart
can't start a request older than 60 s.
### V2 launch
1. At the verb the broker checks that I exists, isn't a thread instance (`not-thread-launch`: threads start
on a message), isn't retired, and that the business isn't revoked and, with row 191, isn't halted. These
repeat at the take (D1.1 item 2).
2. After the take, `check(I, { human: true })` and `start({ instance: I, launchedBy })`, where
`launchedBy` is the business human or `tester`. `human: true` lets the human launch an instance that
isn't in `launch.instances`. The listing limits the chief, not the human. `launch.by` still goes through
the `pm: true` branch (`launcher.mjs:235`), as `--pm` does today. Every other step of `check()` and
`start()` is unchanged.
### V3 terminate
1. The broker revokes every claim I holds (`#claimEnd(s, h, 'revoke', null, reason, actor)`). A thread
instance has all its thread claims revoked, which is exactly `assistant.stop`.
2. In the same transaction it writes `lifecycle.cancelled` for every open (untaken) request of I, so no
launch the human asked for before the terminate starts after it (B5).
3. It writes `lifecycle.end` with the runs. The launcher pass sends SIGTERM to each run's runner after
checking its start time, as the idle stop does (`launcher.mjs:526`). It also signals any run of I in the
launcher's registry that has spawned but not yet bound, which holds no claim yet. The existing exit
handler records the end, so the launch journal and `session.ended` are unchanged.
4. Nothing held, nothing open and nothing in the registry answers `not-held`.
5. Terminate is allowed while row 191's switch is on. It starts nothing.
A taken launch request is past cancelling. Its run reaches the registry under `serialize`, and the next
terminate ends it. The CLI's terminate output says so when a taken request exists.
There is no SIGKILL escalation in this row. A run that ignores SIGTERM keeps running. `mosaic stop` and the
limits are the operator's tools for it (N6).
### V4 regenerate
A fresh session with the same identity and role. It is V3 then V2 under one request. The pass takes the
request, waits for the old run's exit handler, then runs `check()` and `start()`. The workspace stays, as
it does across every launch today. The session is new because every non-thread launch gets a new run dir
(`seat/src/session.mjs:107`, newRun). If the old run hasn't exited by the request's 60 s mark, the request
closes as `regenerate-timeout`, nothing starts, and the old run is still running (N6).
A thread instance (the assistant) refuses `regenerate-thread`. Its context lives in the thread's session
directory, and clearing that is T1 item 7 (clear context). This row doesn't take it.
### V5 delete
`instances.set` gains op `delete`. At the verb the broker and the writer refuse, in this order:
| Refusal | When |
|---|---|
| `tester-refused` | the caller is the tester (B6) |
| `organization-definition-refused` | I is END_PROTECTED |
| `organization-instance-referenced` | I is `launch.by` or an arbiter. The message names the reference |
| `organization-instance-kept` | I is in `launch.keep`. The human runs `veto unkeep` first. Delete never clears a veto silently |
| `organization-credentials-out-of-scope` | I holds credentials or a tracker bot. That stays a hand edit until the credentials row (row 199) |
| `instance-running` | the broker sees a claim for I. The human terminates first |
| `instance-starting` | an open or taken request for I has no done event |
| BusinessError | I is the last name in `launch.instances` (E1). The message says so |
The checks pass with no write, and the broker records a `lifecycle.delete` request. After the take, the
launcher pass, under `serialize` so no `start()` of I runs alongside it:
1. Re-checks the registry for I (`instance-running`) and re-runs the writer's checks against the file as it
stands. The `--revision` the human gave must still match, else `revision-conflict`.
2. Commits the file through the organization writer. It removes `roles.I` and the references that exist
only for I: its `launch.instances` entry, its `launch.limits.tokens.instances` entry and its
per-instance model entry. It adds I to a new top-level list, `retired`. The build lists every place
`validateBusinessDocument` reads a role instance name and puts each one in one of the two groups (refuse
or remove). A reference the table doesn't name stops the build and goes back to Webby.
3. Renames `workspaces/<b>/I/` to `workspaces/<b>/.retired/I-<transaction>/`, mode 0700. No workspace is
no rename.
4. Calls `lifecycleDone` with the transaction.
The file goes first. Once it is committed, `check()` refuses I as unknown, so no launch can open the old
workspace even if step 3 fails. A failed rename closes the request with `workspace-rename-failed`, the
transaction and the path left behind. A second `delete` of a retired name skips steps 1 and 2 and retries
step 3 only. The file and evidence are already right, so this is the whole recovery.
What stays:
- Run evidence (`launches/<b>/<run>/`, `launches/<b>.jsonl`) and bus rows are untouched. They are
write-once.
- The workspace, under `.retired`. This row deletes no file. A prune with a receipt is a later act.
- `retired` stops a later create or hire from reusing the name. Decisions bind to the role instance and
outlive restarts (`broker.mjs:783-788`). A new `coder` would otherwise inherit an approval raised by the
old one. The tradeoff is a name that can't come back without a hand edit. I chose that over voiding
decisions, which would touch a second store.
### V6 veto
`veto.set` args: `{ revision, op: "keep" | "unkeep" | "nohire" | "hire", name }`. It goes through the
organization writer, so it is logged with actor and diff in `changes.jsonl`. `keep` needs a declared,
non-END_PROTECTED instance. `nohire` needs a name that matches the id pattern. A no-op answers
`organization-no-change`. It is human-only and isn't in `TESTER_ORGANIZATION_VERBS`.
## D2. Chief hire and fire
### D2.1 Authority (Mos, mos-1015 and mos-1017, through Sage, sage-1362 and sage-1365)
This row adds `role.hire` and `role.fire` to `ACTIONS` (`vocabulary.mjs:11`). Under PRD REQ-WAIT-3 a new
action is a hard wait until a PRD version says it isn't (`vocabulary.mjs:52-57`), so both ship as hard
waits. Jason resolves each one. There is no PRD change and no grant in a role file: `role.mjs:42` refuses
a withinRole that lists a hard wait, and REQ-WAIT-2 forbids it.
The flow in this row:
1. The chief raises a `role.hire` or `role.fire` decision whose body carries the arguments (business,
name, definition, vars, or name and reason). A hard wait goes to the human only. No arbiter resolves it.
2. Jason resolves it in the Console or the CLI.
3. The chief calls `instance.hire` or `instance.fire` citing the decision. The broker checks it with
`#consumeAuthority(s, 'role.hire', { decision, target })`, as the `role.revoke` decision branch does,
and the arguments must equal the decision body field by field (N1, webby-0010). For hire that is
`name`, `definition` and `vars` (vars compared with `canonicalJson`, absent equal to absent only). For
fire it is `name` and `reason`. A key in the call that the body lacks, or a body key the call lacks,
refuses `decision-mismatch`. Every check below runs again at this call, so a veto set after the resolve
still refuses.
The routed form, where the chief hires and fires without a decision each time, needs a PRD version that
moves both actions to `ROUTED`. My draft is `agents/honker/work/queue-194/PRD-1.7-DRAFT.md`. It quotes
TARGET.md line 66 and carries Mos's bounds. Jason approves it as a `prd.approve` decision in the Console,
and Sage opens that decision. When it lands, a later commit adds the two actions to both `ROUTED` lists
(`vocabulary.mjs`, `broker.mjs:43-69`) and to `roles/chief-of-staff.json` withinRole. The broker then
takes the `reason` form instead of a decision. The verbs and every check below stay the same. This row
builds and tests the hard-wait form only. It writes the `hireable` key and its allowlist now (D2.3), because
both forms use them.
### D2.2 What the chief may do
| Act | Bus verb | Rule |
|---|---|---|
| launch | `launch` (host) | as today: listed instances only |
| end a run | `role.revoke` + reason | as today, and refused `instance-kept` when the target is kept (D2.4) |
| hire | `instance.hire { name, definition, vars?, decision }` | adds and lists an instance. Refusals below |
| fire | `instance.fire { name, reason, decision }` | ends I's runs, cancels I's open requests, and removes I from `launch.instances`. Refusals below |
Only the business's chief, the instance named by `launch.by`, may call either verb. Any other caller
refuses `role-not-chief`, even with a resolved decision, as `#classification` already limits
`role.launch`. The broker reads `hireable` from that chief's own role file. Both act only in the chief's
own business. Neither verb takes a business argument. The broker uses the
cap's business, and a decision raised in another business refuses `decision-mismatch`. Row 191's switch
refuses both while halted (D6). Every hire and fire writes `changes.jsonl` through the organization writer
with actor the chief's instance and run, and the decision id.
Hire refuses, in order:
1. `definition-not-hireable` when the definition holds an action outside `HIRE_ALLOWED` (D2.3), whatever
any list says.
2. `definition-not-hireable` when the definition isn't in the chief role file's `hireable` list, or is in
the business's `launch.noHire`.
3. `organization-definition-refused` for END_PROTECTED, `organization-credentials-out-of-scope` for a
credentialed definition (until the credentials row, row 199), `invalid-request` for version 1.
4. `organization-instance-retired` when the name is retired.
5. When the name exists: a fired instance (in `roles`, not listed) is listed again only if `definition`
equals its current one and `vars` is absent. Any other definition or vars refuses
`organization-instance-exists`, and so does a listed name. A re-hire never moves an instance's
decisions to a different role (B3).
Hire's `vars` may hold only the keys the chief can already set on an instance through `organization.set`
(the model key, `broker.mjs:1266-1269`) and narrowing `limits.*` keys. Anything else, such as `harness`
or a route, refuses `invalid-request`. The human sets those (N3).
Fire refuses `instance-kept` for a kept instance, `organization-definition-refused` for END_PROTECTED,
`organization-instance-referenced` for `launch.by`, and a BusinessError for the last listed instance. A
fired instance stays in `roles`. The chief never deletes. Delete stays a human act because it retires the
name.
Fire ends the fired instance's processes, not only its claims (F4, Megavolt through sage-1374). Every run
gets the shared model token at start: `ENV_ALLOW` (`packages/seat/src/session.mjs:83-94`) passes
`ANTHROPIC_API_KEY`, `CLAUDE_CODE_OAUTH_TOKEN` and the other provider variables into the sandbox. A revoke
can't take that token back, so a fired agent whose process lives on still holds it.
The delist is a business file commit through the organization writer, and the revoke and events are store
rows. No transaction covers both (webby-0011 B2), so fire runs in a fixed order, store side first:
A. One store transaction: steps 1 to 3 below, plus `fire.stopped` with the decision id. The decision isn't
consumed yet.
B. The delist through the organization writer (`launch.instances` loses I, logged in `changes.jsonl`).
C. One store write consuming the decision and recording `fire.done`.
If B or C fails, fire answers the step's code and the decision stays unconsumed. A second fire citing the
same decision runs again. A finds nothing to revoke, cancel or stop, and that isn't a refusal for fire
(unlike V3's `not-held`). B finds I already delisted and counts as done. C consumes. So a fire is safe to
repeat until it's consumed. The other order would leave a delisted agent with a live process and its model
token when the store step fails, which is the case F4 exists to stop. A halt between A and B doesn't stop
B or C, because neither starts anything. A second fire while halted refuses (D6), and the halt's own stop
covers the runs.
Steps 1 to 3 are V3's, with one change:
1. It revokes every claim the instance holds.
2. It writes `lifecycle.cancelled` for each of the instance's open requests.
3. It writes `lifecycle.end` with the runs and `kill: true`. The launcher pass stops each runner, spawned
but unbound registry runs included, with escalation (sage-1380 item 1):
- SIGTERM after the identity check.
- After the grace, SIGKILL to any runner still alive, after the same identity check again, and
`lifecycle.end.killed` with the run and pid.
- A runner still alive after SIGKILL and the observe wait gets `lifecycle.end.alive`. The Console's
Agents section shows it to the human.
A runner whose identity no longer matches is never signalled. Every check, the recheck after the
grace included, uses the identity (start ticks plus boot id), never a bare pid, so a reused pid never
counts as the fired run (webby-0011 N2).
Fire's answer lists the runs it is stopping. The delist stops any new chief launch (`launch` takes listed
instances only). A human launch request already taken is past cancelling, as in V3. The human made it, the
fire answer names it, and the human's terminate ends its run.
The stop is row 191's per-run stop, not a second one, so the two can't fight (sage-1380 item 1). Row 191's
design r2 (`agents/rocko/work/queue-191/DESIGN-r2.md`, "Stop policy and completion") sends SIGTERM, waits
5 s, sends SIGKILL to verified groups and namespaces, and observes for 2 s. Its identity check is start
ticks plus boot id, which replaces the start-time check at `launcher.mjs:526`. Fire calls that routine for
one instance's runs and uses its constants, so the grace is row 191's 5 s, not the 10 s in r3. The order:
- Fire is refused while halted (D6), so a halt sweep never meets a fire that started after it.
- A halt that comes during a fire's stop finds those runs already stopping. Each run has one stop receipt
(row 191's), and the first stop owns it. The halt sweep joins it instead of sending a second SIGTERM or
restarting the deadline, and counts the run in its remaining or quiescent totals.
- The record says which stop owned it: reason `fire` with the decision, or row 191's `kill-switch` with
its generation.
V3 terminate is unchanged. It still sends SIGTERM only (N6). Only fire escalates, because only a fired
agent must lose the token at once. If row 191 hasn't landed when this row builds, the build chains on its
candidate (Q177), as D6 already says.
### D2.3 The hire allowlist (B2, sage-1365 item 4, webby-0010 B1)
r2 and r3 had a deny list, `HIRE_FLOOR`. Webby's B1 showed it missed routed actions (`git.merge.protected`,
which `roles/merge-gate.json` holds with no credentials, `decision.resolve.technical`, `policy.change`,
`prd.approve`), and that any action added later would be hireable by default. That isn't fail-closed. r4
replaces it with an allowlist.
A definition is hireable only if every action in its withinRole and crossRole is on `HIRE_ALLOWED`:
| Allowed action | Held today by |
|---|---|
| `message.send`, `record.write` | every v2 role file |
| `review.request`, `task.update.assigned` | coder, cto, pm, reviewer |
| `task.scope.change`, `git.push.working` | coder, cto, pm (credentialed, so they matter only after row 199) |
Everything else in `ACTIONS` fails, including the actions of the old floor (`role.launch`, `role.end`,
`role.revoke`, `organization.change`, `instance.name`, `review.verdict`, `role.hire`, `role.fire`) and those
B1 named. An action added to `ACTIONS` later fails until a reviewed commit adds it here.
`git.push.remote` (row 199, routed to coder by PRD 1.7) isn't on the list. A coder holding it stays
unhireable until a reviewed commit adds it, after the credentials bound is lifted.
Against today's role files: chief-of-staff, cto, merge-gate, pm, reviewer and security-reviewer fail.
Assistant, researcher, coder and the five C-suite files pass the allowlist. Assistant is END_PROTECTED and
coder is credentialed, so D2.2 item 3 refuses them. The C-suite files hold only `message.send` and
`record.write`, so hiring one gives no authority beyond its name. `hireable` still has to list it.
The list is a constant in `business/src/vocabulary.mjs`, `HIRE_ALLOWED`. It is checked twice:
- At load. `role.mjs` validates the chief role file's `hireable` list: distinct definition names, each
present in `rolesDir`, version 2, and every action on `HIRE_ALLOWED`. A list naming one that isn't refuses
the role file (`hireable names <def>, which holds <action>`). The check reads only the named file's authority lists,
never its `hireable`, so a list can't recurse. `hireable` is an optional key on any role file. This is a
role schema change, named for review.
- At hire, in the broker, against the definition as loaded then. A role file edited after load is caught
here.
`hireable` in `roles/chief-of-staff.json` stays empty in this row (`[]`, Q4, sage-1374). Naming the first
role kind is a separate policy commit in `roles/`, made after Jason resolves the `prd.approve` decision, as
AGENTS.md invariant 7 requires. With an empty list the chief can hire nothing, even with Jason's resolve,
until that commit. The row's tests use scratch role files whose `hireable` names a kind.
### D2.4 The per-agent forbid and the veto (sage-1365 item 3)
TARGET.md line 66: "Chief of staff agent has hire/fire authority for agents as needed; unless user forbids
per agent". The forbid is two lists in the business file that only the human writes:
- `launch.keep`: instance names. The chief can't fire, replace or end a kept instance. The broker refuses
`instance-kept` for every chief act aimed at it except launch:
- `instance.fire`, even with a resolved `role.fire` decision. Mos's limit is that Jason's veto applies
to every hire and fire, so the human unkeeps before a fire.
- a hire of that name.
- `role.revoke` in the reason branch (`broker.mjs:1607-1613`). End then launch is a replace (E6).
- an `organization.set` write of that instance's model key (B7). A model change is a replace at the next
launch.
The `role.revoke` decision branch (`broker.mjs:1614-1615`) still ends a kept run. It ends a run, it
doesn't fire or replace, and it cites a decision the human resolved (Webby's Q2 ruling). The human's V3
and the limits also still end a kept run.
- `launch.noHire`: definition names the chief can't hire into this business (D2.2 item 2).
Why the chief can't change the forbid:
- Only `veto.set` writes the lists. It is human-only (`#human(s)` then the D4 hook) and refuses the tester.
- `organization.set`, the chief's one write verb, reaches only `ORGANIZATION_KEYS` (`broker.mjs:145`),
which exclude `launch` and `retired`.
- `instances.set` is human or tester only (`broker.mjs:1248-1298`).
- Hire and fire carry no field that writes either list.
T18 tests each of those paths from a chief session and checks the business file bytes are unchanged.
END_PROTECTED instances can't be kept (N7). That is intended: Q169 lets the chief end the assistant's
runs, and the assistant's own stop is `assistant.stop`. Default, per "unless user forbids": nothing is
kept and nothing is on `noHire`.
### D2.5 Where it lives and how the broker enforces it
The forbid lives in the business file, under `launch`, beside the membership it limits:
`launch.keep: [instance]`, `launch.noHire: [definition]`, and top-level `retired: [instance]`. `hireable`
lives in the chief's role file, because it bounds the role, not one business.
`checkLaunch` (`business.mjs:118`) gains the two keys. Each list is distinct. `keep` names declared,
non-protected instances. `noHire` entries need only the id pattern (N2). A stale deny entry is harmless,
and a reviewed commit that removes a role file must not stop every business that lists it. `retired`
names none of `roles`. This is the business-file schema change for review. Old files without the keys load
unchanged. An old bus loader rejects a file that has them (`rejectUnknownKeys`), so a rollback past this
row needs the keys removed first. The activation plan has to say so (R4).
The broker enforces from its live copy of the business, which D3 keeps current. The checks sit in the
`role.revoke` reason branch, in `organization.set`'s model write and in the two chief verbs, before any
write.
## D3. Live changes
Today the broker's `#businesses` is fixed at start (E3), and `instances.set` answers `restart: true` for
add and redefine.
1. `Broker.reloadBusiness(id, document)` swaps in one business's validated document. It is synchronous, so
no request sees half of it. It refuses an unknown id and a document whose `human` changed (that is row
193's ground).
2. The runtime's organization callback (`runtime.mjs:121-133`) calls it in one place, after every verb
that commits a write, whatever the verb (N1). Today that is `limits.set`, `instances.set`, `routes.set`
and `plugins.set`, and after this row `veto.set`, `instance.hire`, `instance.fire` and the launcher's
delete through the same writer. It loads from the file through the loader `bootConfig` uses
(`cli/src/config.mjs:88-105`), so the broker holds exactly what a restart would give it. Then
`limits.refresh(id)` runs, as `plugins.set` does now.
3. Other startup consumers of `businesses` in `startBroker` (`runtime.mjs:75-81`): credential references,
usage, the web services and the tasks adapter's bots. A create or hire adds no credentials, no tracker
bot and no web grant, because credentialed definitions are refused. The build checks each consumer
against that claim. Any change that would need one of them still answers `restart: true` and doesn't
reload.
4. A definition change on a running instance refuses `instance-running`. A live cap must not change
authority under its run. This is new: today the change waits for the restart. Existing tests that pin
`restart: true` change with it (N4): `business/tests/instance-settings.test.mjs`,
`bus/tests/instances-verb.test.mjs`, `cli/tests/instances.test.mjs:76-94` (the `RESTART_NEEDED` text and
the restart line) and `webui/tests/instances.test.mjs:25, 71, 74, 140-145`. The build names each
changed assertion in its manifest.
5. The chief's `launch` tool (E5) takes `instance` as a string with the id pattern instead of the start-time
enum. Its description says the host refuses an unlisted instance. `check()` already refuses one
(`launcher.mjs:235`). Without this, the chief can't launch its own hire until its session restarts.
`tools.mjs` may carry hash pins in tests. The build greps for them (R5).
6. Hand edits of the business file still need a restart. Nothing watches the file. The one exception is
`bus.tester` (D4.1), read from the file at each tester call.
After this, `restart` is false for create, delete, hire, fire and veto changes, and true only under item 3.
## D4. RBAC until row 193
Every human verb above calls `#human(s)` and then one hook, `#mayHuman(s, verb, target)`. Until row 193
lands, the hook passes for the business's one `human`, which is the owner. The verbs are listed once, in
an exported `LIFECYCLE_HUMAN_VERBS` (`instance.launch`, `instance.end`, `instance.regenerate`, `veto.set`,
and `instances.set` ops `add`, `change`, `delete`), so row 193 maps each verb to the human roles that may
use it. Row 193 replaces the hook body and nothing else.
### D4.1 The tester (Sage, sage-1355)
What the tester may do after this row:
| Verb | Tester |
|---|---|
| `instance.launch`, `instance.end` | yes, new |
| `instances.set` ops `add`, `change` | yes, as today (row 175, `broker.mjs:103`, `serve.mjs:537`) |
| `instances.set` op `delete` | no, `tester-refused` in the broker (B6) |
| `instance.regenerate`, `veto.set` | no, `tester-refused` |
The tester's cap is bound to one business, and only when that business sets `bus.tester` to true
(`broker.mjs:542-546`). That stays Jason's switch, and no bus verb writes it. Two checks run on each tester
lifecycle call, before any effect, and again at the take (D1.1):
1. The request's business is the cap's business. Any other refuses `tester-refused`.
2. `bus.tester` is exactly true in the business file as it stands, read through `settings.current(id)` as
`harnessOn` does (`runtime.mjs:136-142`), not from the broker's copy (B8). An unreadable file refuses
`tester-off`. So a hand edit turning testing off counts at the next tester call, without a restart.
The request and done events record actor `tester`. The tester's Q173 tests of the Agents section then cover
launch and terminate on a scratch business, and the rest through fixtures.
## D5. One stack instance
Row 190 carries the stack instance as the config `MOSAIC_CONFIG` names. These verbs take no instance name.
The CLI reaches the broker socket under that config's `dataRoot`. The broker acts on its own businesses,
launcher and data root. A business id that exists in two stack instances is two separate businesses. A test
runs two scratch roots with the same business id and shows a launch, a terminate and a delete in one leave
the other byte-identical (T11).
## D6. The kill switch
- Launch and regenerate end in `start()`, which row 191 gates (its D3 inventory, `launcher.mjs:329-457`).
The new pass adds no second start path.
- The broker refuses `instance.launch` and `instance.regenerate` with `processing-halted` while the switch
is on (V2 step 1), and the take checks again (D1.1), so no request is waiting when a human resumes.
- `instance.hire` and `instance.fire` are agent work verbs, so row 191's default refuses them while halted.
The build adds them to row 191's work table, not its read list. A hire Jason resolved before a halt
still refuses while halted, and the chief calls it again after the resume.
- Terminate, create, delete and veto start nothing and stay allowed.
- If row 191 hasn't landed when this row builds, the build chains on its candidate (Q177). The two halted
checks are written against its reader and tested with its fixture.
## D7. The Console
A new Settings section, "Agents", in its own `agents.js` file. Settings itself isn't rewritten. It lists
each instance with definition, listed, kept, retired and running runs, and flags a run with a
`lifecycle.end.alive` event as "still running after fire" (D2.2). It has these actions:
- Create (definition select, from `definitions`), Delete (with a confirm naming what stays: the evidence
and the workspace under `.retired`).
- Launch, Terminate (with an optional reason), Regenerate (with a confirm).
- Keep and Unkeep per instance, and a No-hire toggle per definition.
The routes are `POST /api/instances` (op `delete` joins `add` and `change`), `POST /api/agent/launch`,
`/api/agent/terminate`, `/api/agent/regenerate` and `POST /api/veto`. Each uses `instanceSend`'s boundary:
signed-in user, exact Origin, same-origin Fetch Metadata, JSON within 16 KiB, strict keys, one bus call
through the human transport, and `outcome-unknown` on an unexpected answer. Launch and regenerate show the
request, then poll `GET /api/agent/request?id=` until it closes.
`agents.js` joins the static file list (`serve.mjs:28`) and `index.html` (N5).
When `/api/config` says `send` is false, every action button shows as unavailable with the text "Agent
actions are unavailable here. Use mosaic business instance from the instance CLI." This is the live state
until Q175 phase 2. Every write test uses a fixture server and fixture writers, as rows 165 and 191 do. No
route gets a `--send` exception.
## D8. Tests
Every test uses scratch roots under `TMPDIR` and the existing fixtures (`business/tests/helpers.mjs`,
`cli/tests/helpers.mjs`, `webui/tests/tester-fixture.mjs`). None touches `~/.mosaic-dev`,
`~/.config/mosaic-dev`, a live unit or a slot file.
| Test | Shows | File |
|---|---|---|
| T1 | V1 create on a running bus: a launch of the new instance binds with no restart, and the answer says `restart: false` | cli `instances.test.mjs` |
| T2 | V2: a human launch of an unlisted instance runs. `launch.by` goes through the `pm` branch (N8). A thread instance, a retired one and a revoked business refuse at the verb. A request older than 60 s never launches. A refused `check()` closes the request with its code | cli `lifecycle.test.mjs` (new) |
| T2a | Take: a `launch.revoke` between the request and the pass closes it `launch-revoked` with no process. A `lifecycleDone` that throws once leaves the request taken, and the next tick starts nothing. A taken request is never taken twice | same |
| T3 | V2 with `--no-launch` and with each limit refusal: no process, request closed with the code | same |
| T4 | V3: claims revoked, runner SIGTERMed after the start-time check, one end record. A mismatched start time sends no signal. An open request is cancelled and never starts. A spawned, unbound run in the registry is signalled | same |
| T5 | V4: the new run starts only after the old exit, with a new run dir and the same workspace. A run that ignores SIGTERM closes the request `regenerate-timeout` with nothing started. The assistant refuses `regenerate-thread` | same |
| T6 | V5: each refusal in the V5 table, in order, including `instance-starting` with an open request. A clean delete leaves the evidence bytes unchanged, commits the file before the rename, adds `retired`, and a create or hire of the same name refuses. A failed rename closes `workspace-rename-failed`, `check()` refuses the name, and a second delete renames only | business `instance-settings.test.mjs` and cli `lifecycle.test.mjs` |
| T7 | V6: each op, the no-op refusal, an unknown `noHire` name accepted, and a `changes.jsonl` entry with actor `human` | business |
| T8 | Chief hire (hard-wait form): no decision refuses. A decision raised by the chief goes to the human only, no arbiter. After the resolve, the hire adds and lists the instance, logs actor and decision, and the same chief session launches it (D3 item 5). Arguments that differ from the decision body refuse. A non-chief caller with a resolved decision refuses `role-not-chief`. Outside `hireable`, in `noHire`, credentialed, END_PROTECTED, retired, and extra vars each refuse | bus `chief-of-staff.test.mjs` |
| T8a | Re-hire (B3): a fired name with the same definition and no vars is listed again. Another definition or vars refuses `organization-instance-exists` | same |
| T8b | Hire allowlist (D2.3): one case per action in `ACTIONS` that is neither on `HIRE_ALLOWED` nor a hard wait (`role.mjs` already refuses those), looped from the lists, not typed by hand. A fixture definition, clean when the chief's role file loads and listed in `hireable`, is rewritten to hold that action before the hire. The hire refuses `definition-not-hireable`. A definition holding one allowed and one refused action refuses. One holding only allowed actions hires. A pin test fixes `HIRE_ALLOWED` to the six actions | same |
| T8c | Allowlist at load: a role file whose `hireable` names a definition holding each refused action refuses at `loadRole`, one case per action, looped the same way. The mixed case refuses. A clean list loads. A missing, version 1 or duplicate name refuses | business `role.test.mjs` |
| T9 | Chief fire: runs end, open requests cancelled, delisted, still in `roles`. Kept (even with a resolved decision), END_PROTECTED, `launch.by` and last-listed refuse | bus `chief-of-staff.test.mjs` |
| T9a | Fire ends processes (F4, sage-1380 item 1): with a bound run and a spawned but unbound registry run of I, both fixture runners holding a stand-in model variable, fire answers both runs. After the launcher pass, neither runner pid is alive and I holds no claim. A fixture runner that ignores SIGTERM gets SIGKILL after the grace, is gone, and has `lifecycle.end.killed` with its run and pid. A stand-in whose kill is stubbed to fail gets `lifecycle.end.alive`. A runner whose identity changed before the SIGTERM, or between SIGTERM and SIGKILL, gets no signal. V3 terminate of a SIGTERM-ignoring runner sends no SIGKILL | cli `launcher.test.mjs` |
| T9b | Fire and row 191's halt: a halt during a fire's stop sends no second SIGTERM, keeps the fire's deadline, writes one stop receipt with reason `fire` and the decision, and counts the run in its totals. A fire while halted refuses (D6) | cli `launcher.test.mjs`, on row 191's fixture |
| T9c | Fire order (webby-0011 B2): the organization writer stubbed to fail after A commits. Fire answers the writer's code, I's claims are revoked and its runs stopped, I is still listed, and the decision is unconsumed. A second fire citing the same decision delists I and consumes it. A third fire refuses `decision-consumed`. With C stubbed to fail, the second fire finds I delisted and only consumes | bus `chief-of-staff.test.mjs` |
| T10 | Keep: chief `role.revoke` with a reason, fire, hire of that name and an `organization.set` model write aimed at a kept instance each refuse `instance-kept`. A `role.revoke` citing a human-resolved decision ends the kept run (Q2). The human's V3 works | same |
| T11 | Two scratch stack instances with the same business id: V2, V3 and V5 in A leave B's root and config byte-identical. A chief of business A can't hire into business B on the same bus | cli `lifecycle.test.mjs`, bus |
| T12 | Agent and reader sessions refuse every human verb before any effect. The tester gets exactly the D4.1 table, including `tester-refused` for `instances.set` op `delete` (B6). The D4 hook is called with the verb | bus `instances-verb.test.mjs` |
| T12a | Tester launch and terminate work on the cap's business with `bus.tester` true, record actor `tester`, and refuse `tester-refused` for a second business on the same bus. A file edit setting `bus.tester` to false, with no restart, refuses the next call and the take `tester-off`. An unreadable file refuses `tester-off` | bus `instances-verb.test.mjs` |
| T13 | `organization.set` with `launch`, `retired`, `keep` or `noHire` keys refuses | bus |
| T14 | Live reload: after each committed verb, `plugins.set` and `routes.set` included, the broker answers from the new document. A definition change on a running instance refuses `instance-running` | bus |
| T15 | Kill switch (with row 191's fixture): launch and regenerate refuse `processing-halted` at the verb and at the take, hire and fire refuse, terminate works | bus and cli |
| T16 | Console: each route's boundary refusals, a fixture write per action, `outcome-unknown` on a bad shape, unavailable buttons when `send` is false, request polling, and `agents.js` served from the static list | webui `agents.test.mjs` (new) |
| T17 | Business file: the new keys validate, each bad entry refuses, and a file without them loads byte for byte as before | business `business.test.mjs` |
| T18 | The forbid can't be changed by the chief (D2.4): from a chief session, `veto.set`, `organization.set` with each list key, `instances.set`, and hire and fire with extra fields all refuse, and the business file bytes are unchanged | bus `chief-of-staff.test.mjs` |
### Mutants
Each one must fail at least one test. They run against the candidate, and the file is restored and its
sha256 checked after each one.
| # | Change | Killer |
|---|---|---|
| M1 | `instance.launch` skips `#human` | T12 |
| M2 | the pass calls `check()` without `human: true` | T2 |
| M3 | the take accepts a request older than 60 s | T2 |
| M4 | the take skips the `launch_state` check | T2a |
| M5 | terminate signals without the start-time check | T4 |
| M6 | regenerate starts before the old exit | T5 |
| M7 | delete skips the running check | T6 |
| M8 | delete skips `retired` | T6 |
| M9 | delete clears `keep` instead of refusing | T6 |
| M10 | delete removes the workspace instead of renaming it | T6 |
| M11 | the `keep` check is removed from the `role.revoke` reason branch | T10 |
| M12 | `noHire` is not read by hire | T8 |
| M13 | fire deletes the instance from `roles` | T9 |
| M14 | `reloadBusiness` isn't called after `instances.set` | T1, T14 |
| M15 | the tool enum is kept | T8 |
| M16 | the halted check is dropped from `instance.launch` | T15 |
| M17 | the Console route skips the Origin check | T16 |
| M18 | `veto.set` joins `TESTER_ORGANIZATION_VERBS` | T12 |
| M19 | the tester's business scope check is dropped | T12a |
| M20 | the tester's `bus.tester` file read is dropped | T12a |
| M21 | hire skips the role file's `hireable` list | T8 |
| M22 | the take doesn't write `lifecycle.taken`, so a request can be taken twice | T2a |
| M23 | terminate doesn't cancel open requests | T4 |
| M24 | delete skips the `instance-starting` check | T6 |
| M25 | delete renames before it commits the file | T6 |
| M26 | the tester's `instances.set` op `delete` refusal is dropped | T12 |
| M27 | the `keep` check is dropped from `organization.set`'s model write | T10 |
| M28 | the `keep` check is added to the `role.revoke` decision branch | T10 |
| M29 | a re-hire accepts a different definition | T8a |
| M30 to M36 | the broker's hire check accepts one action outside `HIRE_ALLOWED`, one mutant per action: `role.launch`, `role.end`, `organization.change`, `instance.name`, `review.verdict`, `git.merge.protected`, `decision.resolve.technical` | T8b |
| M37 to M43 | the same seven, accepted by the load check in `role.mjs` only | T8c |
| M44 | fire skips `instance-kept` when a decision is cited | T9 |
| M45 | `instance.hire` skips the `launch.by` caller check | T8 |
| M46 | `veto.set` skips `#human` | T18 |
| M47 | the broker's hire check needs some action on `HIRE_ALLOWED` instead of every action | T8b |
| M48 | the same in `role.mjs` only | T8c |
| M49 | fire revokes the claims but writes no `lifecycle.end`, so no runner is signalled | T9a |
| M50 | fire's signal skips spawned but unbound registry runs | T9a |
| M51 | fire's stop drops the SIGKILL escalation | T9a |
| M52 | SIGKILL is sent without the second identity check | T9a |
| M53 | a killed runner gets no `lifecycle.end.killed` | T9a |
| M54 | V3 terminate escalates to SIGKILL as fire does | T9a |
| M55 | the halt sweep sends its own SIGTERM and restarts the deadline on a run fire is stopping | T9b |
| M56 | fire delists before the store step | T9c |
| M57 | fire consumes the decision in step A | T9c |
| M58 | a second fire with nothing to revoke refuses `not-held` | T9c |
## Scope and boundaries
- In the row: the verbs and passes above in `business` (vocabulary, role validation, instance-settings,
business validation), `bus` (broker verbs, reload, runtime callback), `cli` (instances command, launcher
pass, host ops), `harness` (the launch tool's schema, and the hire and fire tools), `webui` (routes and
the Agents section), the tests, the package READMEs and the PRD draft in this packet. `docs/TOOLS.md`
text goes to Sage.
- Named for review:
- Vocabulary: `role.hire` and `role.fire` join `ACTIONS` as hard waits, under REQ-WAIT-3. The commit
message names them as hard waits, as REQ-WAIT-3 requires.
- Role schema: the optional `hireable` key, validated in `role.mjs` (D2.3).
- Role file: `roles/chief-of-staff.json` gains `"hireable": []`. No authority change.
- Business file schema: `launch.keep`, `launch.noHire`, `retired` (D2.5).
- Not in the row: the routed form (after PRD 1.7), filling `hireable`, credentialed hires and deletes (row
199), clear context for the assistant (T1 item 7), human roles (row 193), SIGKILL escalation, and any
live activation or Console start.
## Risks
- R1. Case 1's working roles (coder, reviewer, pm, cto) all declare credentials (E11), and pm, cto and
reviewer fail the hire allowlist anyway. Neither the human verb nor the chief can create or delete credentialed
roles until the credentials row (row 199). That is the largest gap against Case 1, and it isn't this
row's to close.
- R2. The human proof is path-bound and same-uid (P19 333). Any same-uid process that runs the tree's
`human-cli.mjs` can launch and delete. Row 190b's separate uid and row 193 narrow it. This row adds no
check of its own.
- R3. The 60 s request window means a slow `check()` (checkouts) can still start a run up to that point
after the CLI gave up. The CLI says so on timeout.
- R4. Rollback past this row needs `keep`, `noHire` and `retired` removed from each business file, and
`hireable` from the role file, or the old loaders refuse them. The activation plan states this.
- R5. `tools.mjs`, `serve.mjs`, `broker.mjs`, `vocabulary.mjs` and `role.mjs` may carry sha256 pins in
other packages' tests. The build greps the tests for every changed path plus a 64-hex value before
in-review, without a `head`.
- R6. With `hireable` empty, the chief can hire nothing until the policy commit in `roles/` that follows
the `prd.approve` resolve names a kind (Q4). Case 1's "as needed" waits on that commit and on PRD 1.7.
- R7. Fire's SIGKILL closes the SIGTERM-ignoring case (sage-1380). A runner still alive after SIGKILL
(an unkillable process, or a stop whose identity check fails) keeps the shared model token until the
human acts on `lifecycle.end.alive`. Only a per-agent model token (row 199 Q-M4, a later proxy row)
would let a fire revoke the token itself.
## Questions and rulings
- Q1, ruled by Mos (mos-1015, mos-1017, through sage-1365): `role.hire` and `role.fire` ship as hard
waits Jason resolves. The routed form needs PRD 1.7, drafted in `PRD-1.7-DRAFT.md`. D2.1.
- Q2, ruled by Webby (webby-0003): keep blocks the chief's `role.revoke` reason branch, not its decision
branch. D2.4.
- Q3, ruled by Sage (sage-1355): launch and terminate are tester verbs scoped to the tester's own
business, behind `bus.tester`. D4.1.
- Q4, ruled by Sage (sage-1374): `hireable` stays empty in this row. Naming the first role kind is a
separate policy commit in `roles/` after Jason resolves the `prd.approve` decision. Tests use scratch role
files that name a kind. D2.3.
## r2 changes against webby-0003
| Finding | Where |
|---|---|
| B1 | D2.1: hard waits under REQ-WAIT-3, no role grant. The routed form is PRD 1.7 (packet draft) |
| B2 | D2.3: the floor from sage-1365 item 4, checked at load and at hire, T8b, T8c, M30 to M43 (an allowlist since r4) |
| B3 | D2.2 hire item 5: a re-hire keeps the definition, T8a, M29 |
| B4 | D1.1: one `lifecycleTake` transaction re-checks and writes `lifecycle.taken`, T2a, M4, M22 |
| B5 | V3 cancels open requests and signals unbound runs. V5 refuses `instance-starting`, runs under `serialize`, commits the file before the rename, and states the recovery. T4, T6, M23 to M25 |
| B6 | V5 table and D4.1: the tester's delete refuses, T12, M26 |
| B7 | D2.4: keep covers `organization.set`'s model write, T10, M27 |
| B8 | D4.1: `bus.tester` is read from the file at each call and at the take, T12a, M20 |
| Q2 | D2.4, T10, M28 |
| N1 | D3 item 2: one reload after every committed write |
| N2 | D2.5: `noHire` takes any id-pattern name |
| N3 | D2.2: hire's vars limited to the model key and narrowing `limits.*` |
| N4 | D3 item 4 names the tests that change |
| N5 | D7 and T16: `agents.js` joins `serve.mjs:28` and `index.html` |
| N6 | V3 and V4: no SIGKILL, `regenerate-timeout` |
| N7 | D2.4: intended, Q169 |
| N8 | V2 item 2 and T2 |
## r3 changes against sage-1374, sage-1377 and Megavolt's F4
| Item | Where |
|---|---|
| Q4 ruled: `hireable` stays empty, the first kind is a later policy commit in `roles/` | D2.3, R6, Questions |
| `instance.name` joins the floor (Webby B2, mos-1023 item 6) | D2.3 table, T8b, T8c, M47, M48 |
| F4: fire ends the fired agent's processes, since `ENV_ALLOW` gives every run the shared model token | D2.2, D7, T9a, M49 to M51, R7 |
| `git.push.remote` routed to coder only (mos-1028 item 3, row 199 r3) | `PRD-1.7-DRAFT.md`, REQ-LAND-2 and the REQ-WAIT-3 append |
## r4 changes against webby-0010, sage-1380 and megavolt-0008
| Item | Where |
|---|---|
| webby-0010 B1: the deny-list floor becomes the allowlist `HIRE_ALLOWED`, so merge-gate, `decision.resolve.technical`, `policy.change`, `prd.approve` and any later action are refused | D2.3, Summary, R1, T8b, T8c, M30 to M43, M47, M48, PRD REQ-COS-3 bound |
| webby-0010 N1: field-by-field compare of the call against the decision body, extra or missing keys refuse | D2.1 item 3 |
| webby-0010 N2: the model write is in REQ-COS-3's text | `PRD-1.7-DRAFT.md` |
| sage-1380 item 1: fire escalates to SIGKILL through row 191's per-run stop, and the order against a halt is fixed | D2.2, T9a, T9b, M51 to M55, R7 |
| sage-1380 item 2 and megavolt-0008: REQ-LAND-2 binds one commit oid, and Megavolt's three text changes against row 199 r4 (e1aa6216c) | `PRD-1.7-DRAFT.md` |
| webby-0011 B1: same as webby-0010 B1, M47 and M48 now allowlist mutants | D2.3 |
| webby-0011 B2: fire runs store side first, then the delist, then the consume, safe to repeat | D2.2 order A to C, T9c, M56 to M58 |
| webby-0011 N1: the remote-owner host check at each push | sent to Megavolt for row 199 |
| webby-0011 N2: identity, not bare pid, at every check | D2.2 step 3 |
| Q4: Webby's view matches Sage's ruling (empty) | no change |
@@ -0,0 +1,327 @@
# PRD 1.8 draft: trusted actions within role guardrails, the manual mode, and grants the chief of staff
# requests and the user approves
Draft by Honker for Vikunja #434, under Mos's assignment mos-1117 (through Sage, sage-1525). Not approved.
It changes nothing until Jason approves it as a `prd.approve` decision. Sage opens that decision and sends
Mos its id. Base: `docs/prd/mosaic-stack.md` version 1.6 at 008746b6e, plus the version 1.7 text Jason
approved (PRD-1.7-DRAFT.md r2, 9ba31b5b3, decision d52febb5, mos-1089). Where 1.7 is not yet written into
the PRD file, this draft's anchors name 1.7's sections.
Revision 2 makes Mos's edits a to f (mos-1124, through Sage, sage-1534) and fixes Duckworth's B1 and B2
with notes N1 to N6 (duckworth-0057) on r1 at 3c30f1036. Revision 3 fixes Duckworth's C1 and takes
notes N1 and N2 (duckworth-0058) on r2 at 60f5ca8fa, with Gizmoduck's enrollment wording
(gizmoduck-0067). Revision 4 makes Mos's rulings on duckworth-0057 (mos-1129, through Sage, sage-1538)
on r3 at d16ccdb91: only an Approve creates a grant, one decision for a grant that makes an account, and
the arbiter route for `host.sudo`. Revision 5 fixes two stale notes from Duckworth's r4 check
(duckworth-0061 N1), with no change to the requirement text. Revision 6 makes Mos's wording for those
notes and adds two items for Jason at approval (mos-1138, through Sage, sage-1548), again with no change
to the requirement text.
For Jason at approval: "The main agent or chief of staff" in Q10 is read as one role under two names,
the chief of staff. So only the chief raises a grant request, and your assistant passes your own request
to the chief. That reading is Mos's (mos-1124). To change it, say so when you approve, and REQ-GRANT-1
changes before the text goes into the PRD.
Also for Jason at approval:
- Only an Approve creates a grant. A grant written into the business file by hand makes nothing. An agent
that runs as the same OS user as the stack can still write the stack's own store. That is the B7
residual (Q182 on dragon-lin, #423 on .53 and .54), and this version doesn't close it.
- Who decides a root command on another host changes. Once `host.sudo` is routed, a raise for it that
cites the spec and isn't within-role goes to the arbiter for its domain, as version 1.7 set for
`git.push.remote`, not to you. The grant and its allowlist still limit the command. In manual mode the
decision comes to you (REQ-TRUST-2).
- Revoke: Q188 says "you, Mos or the tester". "Mos" is read as your assistant (REQ-ROLE-4), limited to
grants in its own organization, and the tester may revoke only on a dev instance. That reading is Mos's
(mos-1124 d, mos-1132). To change it, say so when you approve, and REQ-GRANT-4 changes before the text
goes into the PRD.
- The broker enforces only that the chief of staff raised a grant request. The rest of the chain of
command is the messaging lists and the role contracts, not a broker check (REQ-GRANT-1).
## What it changes
Today an agent acts without a raise only on actions its role file grants within-role, and the role
files grant few. Every other use of a tool or command waits on a decision. Row 210 builds `host.sudo` as a
hard wait, so Jason would decide each command run on another machine. Grants themselves are hand work:
Jason makes every token and key from a runbook (REQ-CRED-1).
Jason's ruling Q188 turns this around. Roles set what each agent may do, and by default agents do it at
will within their sandbox and guardrails. A per-use raise happens only in a manual mode. No agent creates
a grant. The chief of staff brings a grant request to the user's inbox, the user clicks Approve or Deny, and
protected stack code does every step of making the grant. The user's own tokens are entered once in the
WebUI and never reach an agent.
This version adds:
1. Trusted actions within role guardrails as the default mode (REQ-TRUST-1).
2. Manual mode, a list of actions that each need a decision per use: who sets it, where it lives and what
it changes (REQ-TRUST-2).
3. Grant requests, raised only by the chief of staff through the chain of command (REQ-GRANT-1).
4. Approve and the protected grant code, with three grant methods: machine key, Gitea and GitHub
(REQ-GRANT-2).
5. The user's tokens, entered in the WebUI and used only by the grant code (REQ-GRANT-3).
6. Row 210's REQ-CRED-4, generalized from `host.sudo` to every role-listed action that uses a grant
(REQ-CRED-4).
7. Instant revoke (REQ-GRANT-4).
The hard waits stay with the user (Mos, mos-1117): spending money, deleting data, new external accounts,
messages outside the fleet and the rest of REQ-WAIT-1. Approving a grant is the user resolving one of
those hard waits, `credential.mint`. A grant that makes an account, such as a seat's own Gitea user
(Q190), is the same one decision: its method says it makes an account, and the inbox shows the account
name before Approve. So no hard wait moves to an agent.
When this version lands, reviewed commits:
1. add the manual-mode list to the organization file schema, written only on the human's path
(REQ-TRUST-2), and make the broker classify a listed action as needing a decision for each use, before
any other routing.
2. add the grant request: a decision the chief of staff raises with action `credential.mint`, naming the
agent, the method and target, the exact rights and, when the method makes an account, the account
name (REQ-GRANT-1). The broker refuses a `credential.mint` raise from any other role. An
`account.create` raise isn't a grant request and routes as it does in version 1.7, such as a PM's
sign-up for a service.
3. add the grant code that runs on a resolved Approve, one method at a time, and the grant record it
writes (REQ-GRANT-2, Vikunja #435 and #436).
4. add the user's token store, written from the WebUI (or the CLI until the Console knows the signed-in
human) and read only by the grant code (REQ-GRANT-3).
5. move `host.sudo` into both `ROUTED` lists, as row 199 does for `git.push.remote`, and route it under
REQ-CRED-4, with the arbiter route version 1.7 set for `git.push.remote`. Which definitions list it
within-role is a separate reviewed role commit in `roles/`.
6. update the role contracts that list the hard waits: `roles/chief-of-staff.md:75` raises grant
requests with the REQ-GRANT-1 fields, and `roles/pm.md:55` asks the chief of staff for a grant instead
of raising `credential.mint` itself (line numbers on origin/refactor). Other roles that list the hard
waits get the same line as the PM. A role's messaging list carries the chain of command.
## Text for the PRD
Status line, appended to the current paragraph:
> Version 1.8 records Jason's ruling Q188 of 2026-10-20: trusted actions within role guardrails by
> default, a manual mode for per-use raises, and grants the chief of staff requests and the user
> approves, approved by Jason in decision <prd.approve decision id>.
Source, two new bullets above "Version 1.7 source":
> - Version 1.8 source, Jason's ruling Q188 (DECISIONS, 2026-10-20), his answers to Q8 and Q10, in part.
> Q8: "I would rather grant the grant right to an agent that runs with strict guardrails. That agent
> presents the final authorization for the user. Upon approval, the grant is created programmatically.
> I see this as: User configures their PATs, API tokens, authorizations within the WebUI. The agent
> presents the request for grant with X rights for another agent. The user reviews, then clicks
> Approve/Deny. Click of Approve fires off protected mechanical methods to handle the grants on Github,
> Gitea, .... services as needed. The tooling would need to exist and must be guarded." And "Disallowing
> the agents to create grants autonomously is a necessary evil for better security. Requiring the user
> to manually perform the steps is a no-fly." Q10: "Agents should have tools and command access based on
> roles. Those roles dictate the allowed tools and commands. The use of a tool or command should not be
> raised to the user unless the system is configured with a manual mode that requires user
> interaction. By default I want to have the agents capable of running commands and tools at will
> within their sandbox/guardrails. This allows autonomous actions by agents within their lanes. Perhaps
> a "Trusted actions within approved guardrails" methodology." And "The main agent or chief of staff
> would be responsible for surfacing the request to the user's inbox for approval. Not all agents are
> capable or authorized to request directly. It must follow the chain of command properly." He agreed
> with Mos's Q10 points 3 to 6: protected stack code does every step on Approve, his tokens are entered
> in the WebUI and agents never see them, a command on the approved list runs without a click and any
> other is refused, and he, Mos or the tester can revoke a grant at once.
> - Version 1.8 source, Jason's ruling Q190 (DECISIONS, 2026-10-20), his answer "Q12: O1": each agent seat
> gets its own account on his Gitea, created through the Q188 approval path once the Gitea grants exist.
Under "Decisions", after REQ-DEC-6, a new section:
> ### Trusted actions
>
> Added in version 1.8 (Q188).
>
> - **REQ-TRUST-1.** Agents act within their role guardrails without asking. The default mode is
> "trusted actions within approved guardrails". An action a role file grants within-role is done at
> will, inside the agent's sandbox, its tool limit and its grants, and is logged. It raises nothing to
> the user. An action the file doesn't grant within-role still routes by REQ-DEC-2, and a hard wait
> still goes to the user (REQ-WAIT-1). Only a reviewed role change adds an action to a role file, and
> REQ-WAIT-2 still keeps every hard wait out of every role file.
> - **REQ-TRUST-2.** The user can put chosen actions in manual mode. The organization file holds the list
> of actions in manual mode, empty by default. While an action is on the list, every use of it by any
> role becomes a decision for that use, raised to the user, and the action waits until the user
> resolves it. A decision denied, or not resolved, refuses the action. Each decision is used once. For a
> command on another host, the decision names the host, the command and its input. Actions not on the
> list are unchanged. Only the user changes the list, through the WebUI or the CLI. The chief of
> staff's organization writes (REQ-ROLE-3) can't change it, and no agent can. Every change is logged
> with who made it. A change applies to the next use, and a run already in progress keeps no
> exemption.
Under "Credentials", after REQ-CRED-3 (version 1.7):
> Added in version 1.8 (Q188):
>
> - **REQ-CRED-4.** An action a role definition lists within-role, and that uses a grant, runs without a
> decision for each use only when all of these hold:
> - the definition declares the credential kind the action uses;
> - a grant names that agent, and that host or service;
> - for a command, the command matches the grant's allowlist. A grant lists exact argv prefixes. A
> wildcard grant is refused;
> - the agent's key or token for that grant is issued and not revoked.
>
> A command off the allowlist is refused, and adding one takes a new grant request (REQ-GRANT-1). An
> action whose definition doesn't list it within-role still needs a decision for each use. Only a
> reviewed role change adds an action to a definition. `host.sudo` routes under this requirement.
> - **REQ-GRANT-1.** No agent creates or writes a grant. A grant request follows the chain of command,
> and only the chief of staff raises it to the user's inbox. Any other role that needs a grant for
> itself or for an agent it directs asks the chief of staff, through its chain: a worker role through its
> PM, the PM and the CTO directly. The assistant passes the user's own request to the chief of staff
> (REQ-ASSIST-1). The request names the agent, the grant method and its target, the exact rights, and a
> rotate-by or expiry date. The chief raises one only for an agent whose reviewed role definition
> allows that kind of credential, and the broker refuses any other. A grant can't name a wildcard, a
> shell or an empty command. The request is a decision with the action `credential.mint`, so it goes
> to the user as a hard wait (REQ-WAIT-1). A grant whose method makes an account, such as a seat's own
> Gitea user (Q190), is the same one decision: the method in the grant says it makes an account, the
> request names the account, and the inbox shows the account name before Approve. The user's one
> resolution covers the account and its credential, so the hard-waits list doesn't change. An
> `account.create` raise isn't a grant request and routes as version 1.7 has it. The broker checks
> only that the chief of staff raised the request. The chain of command before that is in the
> messaging lists and the role contracts, not a broker check.
> - **REQ-GRANT-2.** The user approves or denies each grant request in the inbox. Approve is the only thing
> that creates a grant, and the code uses a grant only when it has a matching Approve record. No agent
> creates a grant. A grant written into the business file by hand makes nothing. Row 210's hand-written
> grants stay a test path until Vikunja #436a moves `host.run` to grants made by Approve. On Approve,
> protected stack code does every step, not an agent and not the user: it records the grant, makes the
> key or token, installs it on the machine or creates it on the service, and logs each step. On Deny
> nothing is made, and the denial is logged. The grant methods are:
> - machine key: a key for one agent on one enrolled host, with the command allowlist of REQ-CRED-4. A
> host's identity comes from the host side when the user approves its enrollment, never from a first
> connection. A host user with sudo receives a grant only after the user approved that user by name;
> - Gitea: a token for one agent, and for an agent seat its own Gitea user (Q190), with an expiry. The
> all scope and the admin scope are never granted;
> - GitHub: a grant for one agent.
>
> The Gitea and GitHub rights units come in a later design that Jason approves.
>
> The approval binds the exact rights the user saw and the target or its enrolled identity. A change to
> the agent, its definition, the target, the target's identity or the rights after the raise voids it. An
> approval is used once. A grant the code couldn't finish is undone at once on the stack's side, so it
> is never used. What it left on the host or service stays visible as pending until it is removed, as
> for a revoke (REQ-GRANT-4). A new grant needs a new approval, never an automatic retry. The grant
> code runs only on a request the user resolved. A request resolved by an arbiter, the tester or any
> agent makes nothing. A `credential.mint` decision raised before version 1.8, or without the fields of
> REQ-GRANT-1, makes nothing when it is approved. Until the Console
> knows the signed-in human (Vikunja #412), Approve is `mosaic decide` in the CLI (REQ-DEC-3).
> - **REQ-GRANT-3.** The user enters their own personal access tokens and API tokens in the WebUI, or the
> CLI. Only the grant code reads them, to make grants. No agent ever sees a token value or a key's
> private half. Values appear in no event, log, message, prompt or agent file, and no agent can read
> them back (REQ-VAR-2). Until the Console knows the signed-in human, they are entered through the
> CLI.
> - **REQ-GRANT-4.** A grant can be revoked at once by the user, by the assistant for a grant in its own
> organization, or by the tester on a dev instance (Q173). A revoke needs no decision and no new trust.
> It takes effect at once on the stack's side, without waiting on the network, so the next use is
> refused. Removing the key or token at the host or service follows. Until that removal is done it stays
> visible as pending. A revoke while the grant is being made stops it, and nothing it made stays usable.
> Every revoke is logged with who made it.
REQ-CRED-1, appended:
> Since version 1.8, a token or key for a grant is made by the grant code on the user's Approve
> (REQ-GRANT-2), not by hand. Tokens that exist stay as they are.
REQ-WAIT-3, appended:
> `host.sudo` was added as a hard wait and routes under REQ-CRED-4 since version 1.8.
Under "Risks and open questions", a new bullet:
> - Trusted mode lets an agent run its allowed commands at will. On another host, an allowed command that
> writes a file as root or starts a shell can plant a key, and the seat sandbox doesn't confine the
> network, so root can outlive the grant and a revoke. The allowlist narrows this. A seat sandbox with
> its own network namespace closes it. Until then the host's own audit log is the check after the fact
> (row 210 design section 6).
## Notes for the reviewer and for Mos
- REQ-TRUST-1 restates what REQ-DEC-2 already does for within-role actions ("routine and within-role
decisions are logged") and names it as the default mode. It grants nothing new by itself. What widens
is the role files, one reviewed commit at a time.
- The hard waits stay with the user (mos-1117). REQ-GRANT-1 makes the grant request a `credential.mint`
decision, already a hard wait under REQ-WAIT-1, so Approve is the user resolving it. REQ-WAIT-2 stands:
no role file grants `credential.mint`. The grant code acts on the user's resolution, not on a role
grant. A grant that makes an account, such as a seat's Gitea user under Q190, is the same one
`credential.mint` decision. So 1.8 doesn't change the hard-waits list (mos-1124 a, mos-1129 N4).
- Manual mode: Sage asked who sets it, where it lives and what it changes. Mos accepted these choices
(mos-1124 b), and Jason can change them: a list of actions in the organization file, empty by
default, changed only by the user, outside the chief's organization writes. A list rather than one
switch follows Gizmoduck's #436 draft section 7 (gizmoduck/machine-grant-design f6b6a04b6), so Jason
can make host commands manual without making every action manual. Listing every within-role action
gives the whole-organization manual mode. Row 210's per-call `host.run` decision, bound to host, argv
and stdin and spent once, is this mode's decision for a host command (Q188, Mos's reading).
- Gizmoduck's draft proposes REQ-CRED-5 (section 8) for the machine grant. Its content is in REQ-GRANT-1
to REQ-GRANT-4 here, generalized to all three methods, so 1.8 has no REQ-CRED-5. Their bounds in
gizmoduck-0062 items 4a to 4g are in the text: the chief raises only for an allowed definition, the
approval binds the rights shown, it is used once with no automatic retry, no value is ever seen, revoke
is at once on the stack's side with the host or service removal visible until done, every grant has a
rotate-by or expiry date, and the host identity comes from the host side. Duckworth has reviewed their
design r1 to r4 (duckworth-0056 and duckworth-0060), so a later change there may need a change here.
- B1 (duckworth-0057, mos-1129): a grant the code couldn't finish is undone at once on the stack's side.
What it left on the host or service stays visible as pending until it is removed, as for a revoke.
- B2 (duckworth-0057, mos-1129): Mos chose Duckworth's second option. Only an Approve creates a grant,
and the code uses a grant only when it has a matching Approve record. A grant written by hand makes
nothing. Jason's Q188 gives the reason: "Disallowing the agents to create grants autonomously is a
necessary evil for better security" and "Upon approval, the grant is created programmatically." Row
210's hand-written grants stay a test path until #436a moves `host.run` to grants made by Approve. r2
and r3 had a hand path in the business file, and r4 takes it out, with r3's load check on hand grants
(duckworth-0058 N1) and the note that a revoke never rewrites the user's file (duckworth-0058 N2).
The same-uid write to the stack's own store stays the B7 residual, and the top of this draft names it
for Jason.
- C1 (duckworth-0058, sage-1538): `account.create` is the hard wait for any new external account, not
only a grant (REQ-WAIT-1, `vocabulary.mjs:43-46`, `roles/pm.md:55`). It stays as version 1.7 has it.
Only `credential.mint` is chief-only, and a grant that makes an account is a `credential.mint`
decision (N4), so a PM's sign-up for a service routes as before.
- Gizmoduck's machine-grant r2 (eb4c756a1, gizmoduck-0067): enrollment is a request the user approves
and the stack runs (mos-1120), and a host user with sudo is enrolled by name, the user's Approve
covering that user (mos-1125). REQ-GRANT-2's machine key says both. Their design r3 and r4 (1c5705d6d
section 6) give revoke to the user, the assistant in its own organization and the tester on a dev
instance, as REQ-GRANT-4 does (mos-1124 d).
- N1 (duckworth-0057, mos-1129): the approval binds the target or its enrolled identity.
- N2 (duckworth-0057, mos-1129): a revoke during creation stops it, and nothing it made stays usable.
- N3 (duckworth-0057, mos-1129): landing step 6 lists the role contract updates (`roles/pm.md:55`,
`roles/chief-of-staff.md:75`). A `credential.mint` decision raised before 1.8, or one without the
grant fields, makes nothing when it is approved.
- N4 (duckworth-0057, mos-1129): a decision carries one action (`a.action`, broker.mjs:1678 on
origin/refactor). A grant that makes an account is one `credential.mint` decision: the method in the
grant says it makes an account, and the inbox shows the account name before Approve. The user clicks
once, and there is no separate `account.create` action for it. Gizmoduck's machine-grant r2 reads it
the same way.
- N5 (duckworth-0057, mos-1129): `host.sudo` keeps the arbiter route version 1.7 set for
`git.push.remote`. A raise for it that cites the spec and isn't within-role goes to the arbiter for
its domain (broker.mjs:1677-1678 on origin/refactor), and the grant and its allowlist still limit the
command. In manual mode the decision goes to the user. The top of this draft names this for Jason as a
change in who decides a root command on another host.
- N6 (duckworth-0057, mos-1129): the broker enforces only that the chief of staff raised a grant request.
The rest of the chain of command is the messaging lists and the role contracts (step 6 of "When this
version lands"). REQ-GRANT-1 says so.
- Approving a grant doesn't by itself let a command run without a click. That needs REQ-CRED-4 and
`host.sudo` in the routed lists, step 5 of "When this version lands" (gizmoduck-0062 item 4i).
- Which roles may request: Q188 says "Not all agents are capable or authorized to request directly. It
must follow the chain of command properly." The text makes the chief of staff the only role that raises,
the PM and the CTO ask it directly, and worker roles ask through their PM. Jason's words also name "the
main agent", but REQ-ASSIST-1 keeps the assistant out of directing agents, so it passes the user's
request to the chief rather than raising one. Mos accepted this (mos-1124 c) and reads "the main agent
or chief of staff" as one role under two names. That reading is Mos's, so the top of this draft puts it
to Jason at approval.
- REQ-CRED-4 keeps row 210's conditions (origin/gizmoduck/row210-on-199-r2,
agents/gizmoduck/work/queue-210/DESIGN.md:376-392): the definition declares the kind, the grant names the
agent, an exact argv-prefix allowlist with wildcards refused, the key issued, and only a reviewed role
change adds the action. Row 210 put the grant in a hand-edited business file, which stays a test path until
#436a. Under 1.8 the grant is what Approve records, so "the business file grants that agent that host"
becomes "a grant names that agent and that host".
- Revoke: Q188 says "you, Mos or the tester". Mos is Jason's assistant (REQ-ROLE-4), so the text says the
assistant, limited to grants in its own organization, and the tester only on a dev instance (Q173).
Mos accepted this with those limits (mos-1124 d).
- Approve in the Console needs the Console to know the signed-in human (Q175 phase 2, #412). Until then
Approve is `mosaic decide`, which REQ-DEC-3 already allows (mos-1124 e). This draft doesn't take
REQ-WEB-2, which stays with the Console decide buttons amendment (Vikunja 19/266). That amendment, or a
later version, moves Approve into the Console.
- Roles already use a scoped Gitea token (`<read|write>:<category>`, all and admin never granted,
`packages/business/src/vocabulary.mjs:105-106` on origin/refactor). It is a likely Gitea unit, not a settled
one. Only the machine key is designed so far. Gitea and GitHub are a later #436 draft, and minting a Gitea
token needs an admin credential the user enters, which is a design of its own (Gizmoduck's draft section
11). GitHub's rights unit isn't settled. So the text leaves both units to a later design Jason approves
(mos-1124 f), and keeps only the bounds: an expiry, and never the all or admin scope. The PRD names outcomes
and checks, not the grant code's steps, as REQ-CRED-3 does.
- Jason expects the inbox Approve and the Gitea and GitHub grants by 10-27 (Q188 point 6). That is a
delivery date, so it is in the source and not in the requirement text.
- This draft follows the PRD's own style: plain words, no semicolons in new sentences, and the status
paragraph never edited in place except to append.
+74
View File
@@ -0,0 +1,74 @@
# Row 256 (#1761) build notes (append only)
## 2026-10-22 b1 (Honker)
Design d2 8c50481d2 was accepted by Webby (webby-0123). The code is on honker/row256 from be2b008cc.
### Correction to the design: the non-creative END_PROTECTED definition is `assistant`
- DESIGN.md (d1 and d2) says "the chief of staff" for the END_PROTECTED definition that isn't creative,
following the brief's wording. In the code that definition is `assistant`:
- `THREAD_DEFINITION = "assistant"` (vocabulary.mjs:203).
- `END_PROTECTED = THREAD_DEFINITIONS = [assistant, ...CREATIVE_DEFINITIONS]`.
- The `chief-of-staff` definition (the `chief` instance) isn't END_PROTECTED. Its delete refuses
`organization-instance-referenced`, because it is launch.by, and that is unchanged.
- So T3 and M2 use an `assistant` instance. The code is the same either way: protectedRefusal refuses
every END_PROTECTED definition that isn't creative.
- DESIGN.md isn't edited after approval; this note is the record.
### Persona file on delete (brief item 4)
- `<configDir>/personas/<business>/<instance>.md` is left in place. Nothing removes it, so there is no
new open and no O_NOFOLLOW test.
- Nothing reads it once the entry is gone. bundle.mjs reads it only for a launched creative instance,
and the name is retired, so `add` refuses it (instance-settings.mjs, organization-instance-retired)
and no later instance picks up the old persona.
- The human removes the file by hand if they want it gone.
### What changed
- packages/business/src/instance-settings.mjs:
- New `protectedRefusal(document, instance, actor)`.
- `deleteRefusal(..., actor)`, `planDelete(..., actor)` and `checkDelete(..., actor)` take the
actor, and set op delete passes its actor.
- `read(business, actor)`. Its `endProtected` is `protectedRefusal(...) !== null`.
- commit's and fire's returned listings read with the writer's actor.
- The comment at :28-31 is rewritten.
- packages/bus/src/runtime.mjs: `instances` and `instances.check` pass the actor. There is no broker
change.
- packages/webui/src/public/agents.js: comment only.
- docs/plans/DEFERRED.md: a closing line on the creative-delete entry.
- Tests:
- business instance-settings.test: T1-T3 and T5 in one test, T4 in its own test, and the row 213
test's `endProtected` for the human's add is now `false`.
- business lifecycle-settings.test: checkDelete with the actor; fire unchanged.
- bus creative.test: T6 on the real runtime.
- webui agents-browser.test: T7/T8. The comment on the existing assertion is reworded.
### Mutants for the gate (exact-once replacements, runb8 shape)
- M1 (instance-settings.mjs): ` if (actor === document.human) return null;` becomes ` return null;`.
The kill is instance-settings.test "row 256 T1-T3".
- M2 (instance-settings.mjs): move the human check ahead of the creative one. Replace
` if (!CREATIVE_DEFINITIONS.includes(definition)) return { code: "organization-definition-refused" };\n if (actor === document.human) return null;`
with
` if (actor === document.human) return null;\n if (!CREATIVE_DEFINITIONS.includes(definition)) return { code: "organization-definition-refused" };`.
The kill is "row 256 T1-T3" (T3).
- M3 (instance-settings.mjs): ` endProtected: protectedRefusal(document, name, actor) !== null,` becomes
` endProtected: END_PROTECTED.includes(entry.definition),`. The kills are "row 256 T1-T3" (T5) and
bus creative.test T6.
- M4a (runtime.mjs): `instances.checkDelete(id,args.instance,actor)` becomes
`instances.checkDelete(id,args.instance)`. The kill is creative.test "row 256 T6".
- M4b (instance-settings.mjs): `planDelete(old, instance, actor), { revision` becomes
`planDelete(old, instance), { revision`. The kills are "row 256 T1-T3" (T1) and creative.test T6.
### Tests not run
None of these have run. They wait for Sage's CLEAR, after the rebase onto 236.
## 2026-10-22 b2 (Honker): Webby's N1 (webby-0124)
- Two comments said the chief of staff where they mean the assistant. Both now name the assistant:
- instance-settings.mjs:30, "The assistant is refused for every actor, the human included."
- agents.js:46, "true for the assistant and on the tester's."
- Comment-only. The mutant strings above are unchanged and still occur once.
+186
View File
@@ -0,0 +1,186 @@
# Row 256 (#1761) design: the human deletes a creative instance
Brief: docs/plans/2026-10-21_creative-delete-human.md (REQ-COS-3). Base be2b008cc (row 194 landed as
fd6dd327c). Builder Honker, reviewer Webby. Draft 2, 2026-10-22: Webby's d1 review (webby-0122, notes
171480e6e) R1, Q1, Q2 and N1-N2 applied; the changes are listed at the end.
## What changes
### 1. One predicate in instance-settings.mjs
Today `deleteRefusal(document, rolesDir, instance)` refuses every END_PROTECTED definition with
`organization-definition-refused`, and read()'s `endProtected` field is the same check
(`END_PROTECTED.includes(definition)`).
New, in `packages/business/src/instance-settings.mjs`:
```js
// Row 256 (#1761, REQ-COS-3): the protected-definition part of a delete. A creative instance is
// the human's to delete; any other actor is refused. A non-creative END_PROTECTED definition (the
// chief of staff) is refused for every actor, the human included.
function protectedRefusal(document, definition, actor) {
if (!END_PROTECTED.includes(definition)) return null;
if (!CREATIVE_DEFINITIONS.includes(definition)) return { code: "organization-definition-refused" };
return actor === document.human ? null : { code: "organization-creative-human-only" };
}
export function deleteRefusal(document, rolesDir, instance, actor) {
...unknown-instance unchanged...
const refusal = protectedRefusal(document, entry.definition, actor);
if (refusal) return refusal;
...references, kept, credentials unchanged...
}
```
- A creative definition with `actor === document.human` goes on to the remaining checks:
references, then kept, then credentials.
- The code and message match `creativeHumanOnly` in launch-settings.mjs:13.
- The code is `organization-creative-human-only`.
- It is raised as a plain refusal code here, as deleteRefusal's other codes are.
- I don't call `creativeHumanOnly` itself, because it throws and deleteRefusal returns.
- `actor` undefined, null, "tester", or a role actor (`<definition>.<instance>`) never equals
`document.human`, so those actors are refused.
- validateBusinessDocument runs `requireId(document.human)` (business.mjs:239), so an undefined
actor can't match an undefined human.
### 2. The actor reaches every delete path
| Path | Today | Change |
| --- | --- | --- |
| `planDelete(document, instance)` | no actor | `planDelete(document, instance, actor)` |
| `set(business, args, actor)` op delete (:207) | has the actor, doesn't pass it | passes it to planDelete |
| `checkDelete(business, instance)` | no actor | `checkDelete(business, instance, actor)` |
| runtime.mjs:283 `instances.check` | `checkDelete(id, args.instance)` | `checkDelete(id, args.instance, actor)` |
- The broker's verb check `#deleteRequest` already passes `s.human` to `instances.check` (broker.mjs:2843).
- The launcher pass `lifecycleDelete` already passes the request's `r.by`, which is
`#owner(s)` at record time, to `instances.set op delete`.
- So the broker gets no logic change. Only the deferred comment at :2825 is touched, if it names
the old rule.
- The CLI (`mosaic instances delete`) and the Console both reach the same bus verb with the human's
session.
- The tester is refused `tester-refused` at the verb, before any of this (broker.mjs:2595,
serve.mjs:394).
- That is unchanged.
### 3. The listing: `endProtected` becomes the per-viewer delete check
- `read(business)` becomes `read(business, actor)`, and runtime.mjs passes the actor.
- The broker already passes `"tester"` for a tester session and `s.human` otherwise (broker.mjs:2627).
- Each instance's `endProtected` becomes `protectedRefusal(document, def, actor) !== null`.
- This is the same server predicate the delete runs, per the brief.
- The human's read shows `endProtected: false` for a creative instance.
- The tester's read, and any role read, show `true`.
- The chief of staff shows `true` for everyone.
- The listing that a write returns is read with the writer's actor.
- This covers `commit`'s `{ ...read(business), ... }` at :405 and fire's at :338.
- So the human's add of bean returns `endProtected: false`, and the existing :288 assertion
changes from `true` to `false` for "jason".
- I keep the field name, so the listing gains no key and the deepEqual shapes in
instance-settings.test, lifecycle-settings.test and agents-browser.test keep their keys.
- Only the value for a creative entry read by the human changes.
- The cost is a name that now means "delete refused for its protected definition, for this
viewer".
- The read() comment says so.
- A rename (for example `deletable`) touches every listing fixture; I'd rather not, but I'll take
Webby's call.
- agents.js:46 is unchanged in logic: `if (!e.endProtected)` offers Delete.
- Its comment changes to name row 256.
- Delete stays inside the existing `!c.isTester` branch, so the tester page offers none whatever
the field says.
- The tester has two guards: the server's `true`, and the page's tester branch.
### 4. Fire is unchanged
- `fireChecks` / `checkFire` / `instance.fire` / `role.fire` keep refusing END_PROTECTED for
every actor.
- protectedRefusal is not called from fire.
- A test pins that the human's fire of a creative instance still refuses.
### 5. Persona file
- `<configDir>/personas/<business>/<instance>.md` is left in place on delete.
- That is the brief's smallest choice.
- The delete commits the business file and renames the workspace under `.retired`, as today.
- The persona file isn't under either path, and nothing reads it once the entry is gone.
- bundle.mjs reads it only for a launched creative instance.
- Retired names are never reused (row 194 D1), so a later hire can't pick up the old persona.
- This goes in BUILD.md, plus a DEFERRED line if Sage wants a cleanup later.
- No new file open, so no O_NOFOLLOW test.
### 6. DEFERRED.md
The "Creative instances: the human can't delete one" entry (DEFERRED.md:387) gets a closing line:
> Closed by row 256 (#1761) for delete. Fire still refuses, by REQ-COS-3.
The entry says "the row covers fire and delete together". The brief keeps fire unchanged, so the
closing line says the fire half is closed by ruling, not by code.
## Tests
| File | Test |
| --- | --- |
| business/tests/instance-settings.test.mjs | T1: the human deletes a creative instance (bean); the file loses the entry. |
| | T2: the same delete by a role actor (`chief-of-staff.chief`), by "tester" and by no actor refuses `organization-creative-human-only`, and the file is unchanged. |
| | T3: the human's delete of the chief of staff refuses `organization-definition-refused`. |
| | T4 (R1): with bean the only name in launch.instances, the human's delete of bean gets the last-listed BusinessError from deletedDocument (:64-66), not the creative code, so it passes protectedRefusal and reaches the later checks. A creative instance can't be in launch.keep, launch.by or the arbiters (business.mjs:208, :181-183, :251-255), so those aren't used. |
| | T5: read(business, human) shows `endProtected: false` for bean; read(business, "tester") and read(business) with no actor show `true`; the chief of staff shows `true` for every actor. |
| | The existing :283-292 test is updated where it pinned the old refusal. |
| business/tests/lifecycle-settings.test.mjs | checkDelete with the human passes for a creative instance and with another actor refuses. |
| | The fire of a creative instance by the human still refuses (fire unchanged). |
| bus/tests/creative.test.mjs (Q2: its runtime() is a real startBroker with the business writer) | T6: the human's `instances.set op delete` on bean records a lifecycle.delete request. |
| | Taking it and running the launcher pass commits the delete (the r.by path). |
| | The chief is refused `human-required` (broker.mjs:2585) and the tester `tester-refused` (:2595), both at the verb. |
| | The human's `instances` read has `endProtected: false` for bean; the tester's has `true`. |
| webui/tests/agents-browser.test.mjs | T7: the human page with bean at `endProtected: false` offers Delete for bean. |
| | T8: the tester page with the same listing offers no Delete. |
| | The existing sage-2164 assertion moves to the tester-shaped listing (bean `endProtected: true`). |
- The browser tests stub `/api/instances`, so they pin the page's use of the field.
- The server side of "never on the tester listener" is T5/T6.
- No test touches live paths: fixtures under mktemp in /mnt/storage/scratch, as today.
## Mutants (all must be killed)
- M1: protectedRefusal's creative branch returns `null` for every actor, so a non-human deletes a
creative instance. Killed by T2 only (N1): through the bus no non-human actor reaches the delete,
since the chief and the tester are refused at the verb.
- M2: protectedRefusal checks the human before the creative check, so
`actor === document.human ? null : ...` for every END_PROTECTED definition. The human then
deletes the chief of staff. Killed by T3.
- M3: read() keeps the old `END_PROTECTED.includes`, so the listing isn't the per-viewer predicate.
Killed by T5.
- M4 (N2): the actor isn't threaded: runtime.mjs:283 calls `checkDelete(id, args.instance)`
without it. Killed by T6, the human's real-writer delete. A second variant, set op delete not
passing the actor to planDelete, is killed by T1 and by T6's launcher pass.
## Boundaries kept
- No change to M10 add/change, role.hire, fire, veto or launch.keep.
- No schema change and no migration.
- No new verb.
- No change to the tester gate.
## Gate (on Sage's CLEAR)
- Touched test files, each whole.
- The business, bus, cli and webui suites.
- Every scripts/test-*.sh.
- The four mutants above (M4 in both variants), each on a green base, restored and blob-checked (the runb8 shape).
## Comments rewritten (Q1)
- `endProtected` keeps its name: agents.js:46 is its only reader, and it means "don't offer Delete".
- instance-settings.mjs:28-31 loses "no one deletes one yet". It says the human deletes a creative
instance and any other actor is refused, and that the listing's endProtected is protectedRefusal
for the reader.
- agents.js:45 says the same, naming row 256.
## Changes from draft 1 (webby-0122)
- R1: T4 uses the last-listed refusal instead of launch.keep, which the validator refuses for a
creative instance.
- Q1: keep the name; comments rewritten; T5 pins read(business) with no actor as true.
- Q2: the bus test goes in creative.test.mjs.
- N1: M1 is killed by T2 only; T6 pins the chief's `human-required`.
- N2: M4 added for the threading.

Some files were not shown because too many files have changed in this diff Show More