Compare commits

...
Author SHA1 Message Date
Della b0844a5ce7 docs: gate mail registry and specify external content keys 2026-10-21 16:45:21 -05:00
Della b096a16cce docs: merge post-v1 email plans and gated build rows 2026-10-21 16:43:07 -05:00
Della 9182134e59 docs: cross-review Ludwig post-v1 email plan 2026-10-21 16:37:18 -05:00
Della 191dff32fb docs: plan post-v1 Stack email processing and monitoring 2026-10-21 16:12:23 -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
180 changed files with 11100 additions and 267 deletions
+22
View File
@@ -5177,3 +5177,25 @@ 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-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.
+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.
+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
+166
View File
@@ -0,0 +1,166 @@
# Row 222 build: C2-03a fetch-only library sync
Row 222, #1723, reviewer Dewey. Brief: C2-03a in the case 2 plan (sage-1853, sage-1854), with
sage-2021 and sage-2022 adding to it. Built on Herb's row 215 candidate source e333af4dc (base
f8276c5eb, packet 14a03d10f, Herb's BUILD.md blob f80f21919d95), then rebased with no conflicts
onto 215's landing e54e05463 (sage-2099) as branch megavolt/row222-r2. The old branch
megavolt/row222 is kept.
Between e333af4dc and e54e05463, two of 222's files changed. broker.mjs gained row 249's
hostLineLeft lines, which 222 doesn't touch. stack-copy.sh gained row 66's `tools/unslop-hook`
in COPY_PATHS, two lines below 222's `packages/library/bin`. In the range-diff, commit 1 differs
only in that context line, and commits 2 and 3 are identical.
## What it does
- `packages/library/src/sync.mjs` (new):
- `syncCommand('fetch', url, opts)` builds the one git command.
- `sync(context, opts)` fetches the configured branch into `refs/remotes/library/<branch>`,
then publishes under the C2-04 library lock:
- a fast-forward moves `refs/heads/served`;
- a non-fast-forward is held as `library-diverged` until a human accepts the tip;
- a failed fetch keeps serving the old commit and records `lastError`.
- `packages/library/bin/askpass` (new, sh) is git's GIT_ASKPASS helper. It answers only the two
prompts for the configured host and user. For the password it reads the first line of the slot
file. The token never reaches argv, env, git config, a URL, the state file or a log.
- `packages/library/src/mapping.mjs`: an optional `user` (the bot account name). Sync refuses an
entry without it.
- Bus:
- `broker.mjs`: `library.sync` is human-only. It takes `{project, accept?}`, and a held code
becomes a recorded refusal.
- `runtime.mjs`: a timer syncs every entry with a `user` at start and every 60 s.
- `webui/src/cli.mjs`: the Console gets the call.
- `scripts/stack-copy.sh` copies `packages/library/bin`.
- Docs: library README "Sync" section, SURVEY-RUN.md step 5.
## Scope rulings followed
- sage-1853/1854: the timer lives in the bus runtime, not a separate unit. The slot is
`<configDir>/secrets/<business>/library-gitea`, mode 0600, owned by the bus uid.
- sage-2021: `user` is optional in the mapping and required for sync. Test runs wait for Sage's
"clear".
- sage-2022 / Filbert N2 (#1716 comment 30071):
- Sync never runs gc or prune and never fetches with `--prune`. `maintenance.auto=false` and
`gc.auto=0` come from SAFE.
- Test "Filbert N2" holds a served commit, lets sync move served, then reads the held commit's
blob.
- The read path is unchanged, so readLibraryFile's whole-tree read stays (see Known costs).
## Choices and tradeoffs
1. **One sync per mirror, refused as `library-sync-busy`.** The C2-04 lock covers publication
only, and readers wait 5 s for it, so the fetch runs outside it. A module-level set refuses a
second sync into the same mirror instead of letting two fetches race. A timer tick that finds
its pass still running is skipped, not queued.
2. **Problems are read before the lock.** readLibrary over a large tree can take longer than a
reader's 5 s lock wait. Problems belong to the tip alone, so they are computed outside and
only published inside.
3. **The served ref moves by compare-and-swap.** `update-ref refs/heads/served <tip> <old>`
(zero id on a first sync). If the state write then fails, the ref goes back, so the ref and
the state file never disagree.
4. **State written via a temp file beside the mirror.** inspectMirror refuses unknown names
inside the mirror, so the temp file goes in its parent directory (O_EXCL|O_NOFOLLOW, 0600).
Then fsync, rename into the mirror, and fsync the directory.
5. **Divergence is held, even through a later fast-forward.** Once diverged, served stays put
until `accept`. `diverged.at` keeps the first detection time, and `diverged.commit` follows
the tip, because that is the commit accept must name. A tip that comes back to the served
commit clears it.
6. **`accept` starts the record over.** It must equal the fetched tip (otherwise
`library-accept-stale`). It writes a fresh record, which also clears a state left from
another branch (readSyncState refuses a branch mismatch).
7. **`lastFetch` is the last successful fetch.** A failed fetch leaves it alone and sets
`lastError`.
8. **`credential.helper=` reset in argv.** It stops a user or system helper from storing or
offering the token. With GIT_TERMINAL_PROMPT=0 from gitEnv, askpass is the only source.
9. **`http.followRedirects=false`.** A redirect to another host would get the prompt for that
host, which askpass refuses anyway. Not following redirects means no request reaches the
other host at all (tested with a 302).
10. **`core.createObject=rename`.** A departure from the brief; see the next section.
11. **Extra env keys.** GIT_CEILING_DIRECTORIES (git never looks above the mirror) and
MOSAIC_LIBRARY_HOST/USER/SLOT (the askpass inputs, no secret). The brief named
GIT_ASKPASS only.
12. **`SAFE` exported from `packages/project/src/git.mjs`.** One word. Sync reuses the same
safe `-c` set instead of copying it.
13. **Row 199's fd-3 helper not reused.** publishRemote (packages/seat/src/working.mjs) puts an
inline `credential.helper` in argv and feeds the token to it on fd 3, so node reads the token
first. The brief names GIT_ASKPASS. With askpass, node only lstats the slot and never opens
it; the helper script reads it. The prompt checks do the same job as row 199's host check.
14. **The timer's test hook.** `startBroker({librarySync: {command, intervalMs, configDir}})` is
refused unless `command` is a function. A JSON boot config can't carry a function, so a boot
file can't set it, and process.mjs needs no change.
15. **No server.mjs edit.** server.mjs awaits every verb in LIBRARY_VERBS with a 120 s socket
timeout. Adding `library.sync` to LIBRARY_VERBS covers it.
## Departure from the brief: `core.createObject=rename`
Sage accepted this departure (sage-2024, rev 2898), and Dewey rules on it in review. The fetch argv
(`syncCommand`, packages/library/src/sync.mjs) is:
```
[...SAFE, '-c', 'protocol.https.allow=always', '-c', 'http.followRedirects=false', '-c', 'credential.helper=',
'-c', 'core.createObject=rename', '-C', mirror, 'fetch', '--no-tags', '--no-write-fetch-head', '--no-recurse-submodules', '--end-of-options',
parsed.href, `+refs/heads/${branch}:refs/remotes/library/${branch}`]
```
Why: by default git files each new object by link, then unlink. While that runs, the object has
nlink 2. C2-04's inspectMirror refuses any file in the mirror with nlink other than 1, so a reader
running during a fetch could be refused. With `rename`, git never makes a second link.
The argv test in sync.test.mjs pins the full sequence, including this flag. The race itself is not
tested (sage-2024).
## Known costs and residuals
- **readLibraryFile reads the whole tree to return one file** (C2-01 exclusions, Filbert's
note on 215). 222 doesn't touch the read path, so it stays.
- **The human transport times out at 5 s, a follow-up for Dewey to rule fix or defer.**
packages/bus/src/human-cli.mjs builds its `Client` (packages/bus/src/client.mjs) with the default
`timeout = 5000`. A fetch can take up to the GIT_LIMITS ceiling (100 s), so a human call to
library.sync against a slow remote can return outcome-unknown while the sync finishes, and the
state file records the result. The outer layers allow longer: busCaller and humanCall in
packages/webui/src/bus.mjs use 30 s, and the bus server awaits library verbs for 120 s.
222 has no Console route to fix. packages/webui/src/serve.mjs has no library route at all
(library.file, library.status and library.sync are passed to startServer and unused there), and
neither serve.mjs nor human-cli.mjs is in 222's paths. Follow-up: when the Console gains a
library.sync route, give that path a 120 s client timeout.
- **Problems are unbounded until the state hits 8 MiB.** A tree with very many problems makes
the state write fail with `library-state-invalid`, and served doesn't move. No cap per
problem list was added.
- **No behaviour test for the `credential.helper=` reset, a follow-up for Dewey (sage-2033).**
Only the argv test pins the reset: mutant 3 (drop it) was killed by that test alone. Today the
reset is defence in depth. gitEnv() sets GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM,
and C2-04's inspectMirror refuses any mirror config key outside its allowlist, so no config
git reads can name a helper. gitEnv() also builds a fresh env, so the caller's env can't add
one. A behaviour test would have to plant a helper through the test's `command` wrapper (for
example GIT_CONFIG_COUNT/KEY/VALUE, the way the CA wrapper adds GIT_SSL_CAINFO), then show
that git never calls it. Left as a named follow-up.
- **stack-copy.test.mjs is not changed.** It copies `ls-files -co`, which includes bin/askpass.
The cli package is not mine.
## Tests
- `packages/library/tests/sync.test.mjs` (new). It uses a fake Gitea over TLS
(`packages/seat/tests/fake-gitea.mjs`), a made-up token and scratch roots, with no network.
It covers the argv and env, the URL and user refusals, askpass prompts, first sync,
fast-forward, Filbert N2, reader overlap, divergence and accept, accept across a branch
change, a wrong token, a failed first fetch, a redirect, slot refusals, no user, and busy.
- `packages/library/tests/mapping.test.mjs`: the `user` field.
- `packages/bus/tests/library-sync.test.mjs` (new): bus refusals for agent, tester and reader;
the human path; a refused boot hook; the timer.
- Run on 4a0e0ab3a under grant sage-2028, each file whole: sync 15/15, mapping 8/8,
packages/bus/tests/library.test.mjs 6/6, library-sync 5/5. No log contains the token.
- Mutants, each against sync.test.mjs whole, all killed. After each one git diff was empty
and askpass stayed 755.
| Mutant | Killed by |
|---|---|
| protocol.https.allow -> protocol.allow=always | argv test |
| drop http.followRedirects=false | argv test, 302 test |
| drop the credential.helper= reset | argv test only (see Known costs) |
| askpass answers any prompt | helper prompt test |
| askpass prints the token for Username | helper test and 8 sync tests |
The last mutant's log holds the made-up fixture token, because the mutant printed it.
- After the rebase onto 215's landing: these 4 files again, plus
packages/library/tests/mirror.test.mjs, packages/webui/tests/library-cli.test.mjs,
packages/webui/tests/pinned-copy.test.mjs and packages/cli/tests/stack-copy.test.mjs.
+14
View File
@@ -1419,6 +1419,20 @@ are never rewritten or removed; corrections are new entries.
2026-10-21T06:47:54Z | researcher | row 228 (#1729) round 3 in review (rev 2702; start rev 2700) | rebase of r2 onto the 234 landing c771a36f4 as researcher/row228-r3 cf18c42b9 (one commit, pushed) with Webby's round 2 N1 (:711 and :718); manifest 82cf98c6 (3 files); slot sage-1796: session.test 13 + host.test 42 = 55/0, manifest OK before and after, busy228.sh copy named in the clear; r2 94/0 and M1 killed stand (seat blobs unchanged); request attempt left in requesting state (lock held by sage-238-review-2b when the outcome rev was written, exit 3; transport said failed pre-send, no token), Sage resolves and posts as jarvis; packet agents/researcher/work/queue-228/r3
2026-10-21T07:29:23Z | researcher | row 228 (#1729) landed as 86249ac55 (pushed 07:25Z by Sage) | patch-id b6ffe31feb0d matches my r3 candidate cf18c42b9 (checked here against origin/refactor); Sage combined gate with 238: seat 165/0, cli 640/0, scripts 585/0; queue note rev 2725, done rev 2727, #1729 closed comment 29966 (sage-1824); q228r2 worktree removed, branches researcher/row228, -r2 and -r3 stay on origin and locally; packets agents/researcher/work/queue-228 r1 r2 r3 uncommitted
2026-10-21T15:19:14Z | Sage (Opus 5.5, 1ef1e4f8) | MVP push 10-21 afternoon: rows 249, 197 proof 3/4, 222 singles, 66 r4d, queue-commit | 249 landed 9de5a57ab (+7b9737953 DEFERRED N1, #1751 closed, Vikunja 445 c442); 197 proof 3 rc=1 on a driver defect, Herb's fix a5b636750 to Webby, proof 4 set up in sage-r197p4; 222 singles 34/34 and 5 mutants killed (sage-2028); 66 round 4 in review (82c830779, c30083/30084); queue revs 2642-2904 committed 0636721e6
2026-10-21T15:20:10Z | Dewey (Opus 5.5, 84fb346c) | row 66 r4 review (sage-2036, sage/row66-r4d 82c830779, manifest 6cfd812da4ca) | APPROVE 30085, rev 2905; rebase only, code lines identical to r3 (-U0 patch-id b3e9e076e5f1), four shared files clean; no tests run; dewey-0768 Sage
2026-10-21T15:20:58Z | Filbert (Opus 5.5, 048f3c63) | row 66 r4 review (sage-2037, sage/row66-r4d 82c830779, manifest 6cfd812da4ca) | APPROVE 30086, rev 2906; static, no tests; rebase only, row 66 +/- lines identical to r3 next to rows 212/213/232/245; N1 code-only patch-id d67275ac4251 (508bb684eade includes BUILD.md), N2 BUILD.md cap-sentence wording; packet-only notes
2026-10-21T18:55:21Z | Filbert (Opus 5.5, 048f3c63) | row 215 r2 merge-scoped review (herb-0093, mos-1160, herb/row215-on197 dde3445ab, manifest 40d9722a) | APPROVE 30103, rev 2924; static, no tests; patch-id d191e7e9586c same as r1, 28/30 blobs+modes = e333af4dc, broker.mjs and stack-release.test.mjs = clean merge-file onto a36523713; r1 N1-N4 carry, suite gate Sage's
2026-10-21T19:06:16Z | Sage (Opus 5.5, 1ef1e4f8) | row 66 landing, #435/J2 brief, 196 r2/215/243/250/251 gate coordination (mos-1186, mos-1193, mos-1195, mos-1197) | row 66 LANDED 2c38325ec + DEFERRED 3b0aef995, #1559 closed; 640aa03f2 brief pushed; REQ-WEB-2 wording to Mos (sage-2067); 196 r2 stop 2 ruled (sage-2072); 215 gate on slot (Herb); #1754 for 243; 250 Gizmoduck/Filbert, 251 Darkwing/Dewey
2026-10-21T19:20:16Z | Darkwing (Opus 5.5, 7525c8e4) | row 251 T27 ssh-add lifetime (sage-2075, sage-2084, Vikunja #453, Gitea #1756) | darkwing/row251 68f2dfd4b pushed (e5e4b2e5a step 1, c19b4167e -t 2 + 3.5 s sleep, host-run.mjs unchanged); in-review r1 rev 2944, request 30121 resolved rev 2947, manifest c7497de3; Dewey woken darkwing-0111; slot asked darkwing-0110, nothing run
2026-10-21T19:23:11Z | Dewey (Opus 5.5, 84fb346c) | row 251 r1 static review (darkwing-0111), 68f2dfd4b, digest c7497de3 | no blocking findings; OpenSSH 10.5p1 citations checked at V_10_5_P1; comment 30126 on #1756; verdict to be recorded on r2 with part A results; no tests run
2026-10-21T19:23:48Z | Filbert (Opus 5.5, 048f3c63) | row 250 r1 static review (gizmoduck-0146, sage-2091, gizmoduck/row250 e9cfd92f8, manifest 14ff20b6) | CHANGES 30125, rev 2950; static, no tests; C1 R13 never calls h.ctrl.close() so the controller server (controller.mjs:548) stays open and cohort.test.mjs is expected not to exit; fix await h.ctrl.close() before h.c.close(); cause trace, step 1, no loosening, R13 coverage, no pins, no drift all hold
2026-10-21T19:28:22Z | Filbert (Opus 5.5, 048f3c63) | row 250 r2 static review (gizmoduck-0148, gizmoduck/row250-r2 c1a858975, manifest eca0b8e2) | APPROVE 30130, rev 2957; static, no tests; C1 fixed: R13 closes its controller after the checks (cached release, one entry) and again in finally (no-op); no drift, no findings; gate run Sage's
2026-10-21T19:31:02Z | Herb (gpt-6.1-sol, f6cf4287) | row 215 C2-04 landed as e54e05463 (#1716 closed c30133, queue done rev 2955) | Filbert round 2 APPROVE c30103/rev 2924; candidate dde3445ab on a36523713 preserves rows 249 and 197. Granted sage-2071: business 209/0, seat 166/0, cli 729/0, project 41/0, bus 604/0, webui 300/0 with one environment editor-build skip; all ten scripts/test-*.sh 585/0; total 2634/0 in 926.796 s, ended 19:20:47Z, leftover process sweep 0, temp root and owned network removed. Sage accepted receipts and landed after 6096b051b (sage-2097); no further 215 work.
2026-10-21T19:34:18Z | Sage (Opus 5.5, 1ef1e4f8) | row 215 landing, 196 r3 grant withdrawn for the base change and r4 granted, 250/222/244/251 slot order | 215 LANDED e54e05463 (patch-id 2f68188b6c9d = a36523713..dde3445ab, tree = merge-tree; herb-0095 2634/0, webui esbuild skip ruled env); #1716 closed c30133, done rev 2955, Vikunja 416 c471. sage-2092 withdrawn (r3 conflicted with 215), sage-2096 to Gander r4 f8a59e6aa, mutant kills kept by blob sha. 250 r2 request c30131 (rev 2958), Filbert APPROVE 2957. 244 issue #1757 (rev 2959). 222 r2 c9aa9c2ee accepted; gate scope per the row. Order: Gander, Gizmoduck, Megavolt, Morgana, Darkwing.
2026-10-21T19:46:12Z | Dewey (Opus 5.5, 84fb346c) | row 222 r1 static review (megavolt-0095), c9aa9c2ee, manifest c2121b55 | APPROVE, comment 30138 on #1723, queue rev 2966; rulings: createObject=rename accepted, 5 s human timeout deferred with STATUS codes in the follow-up, credential.helper reset argv-only accepted; notes N1-N5; no tests run
2026-10-21T19:49:37Z | Sage (Opus 5.5, 1ef1e4f8) | row 250 landing, row 196 r5 grant (mos-1198) and resume (mos-1200), F27 row 253 | 250 LANDED 1b6158ce2 (patch-id 29786caa, Filbert rev 2957, Gizmoduck slot green), #1755 closed c30139, Vikunja 450; 196 r5 91e3696ef hunk checked by Duckworth, webui 304/0; my queue add tripped Gander's busy check, not a stop (mos-1200), resume on 05e940117; row 253 #1758 brief 0cdead66c
2026-10-21T20:46:03Z | Sage (Opus 5.5, 1ef1e4f8) | row 222 landing, PRD 1.9 and 1.10, Pocket ID design row 255, row 254 brief, 196 r6 grants | 222 LANDED 0b33f5e9d (megavolt-0098 gate, Dewey rev 2966, patch-id edb60b4f), #1723 closed c30147, done rev 2972; follow-up row 254 (#1759, after 196); PRD 1.9 f48725cfc (REQ-WEB-2, REQ-DEC-3 amended, mos-1202); 458 brief 7d08237e1 (#1760), row 255 rev 2976; PRD 1.10 add0f1aae REQ-INST-2 post-v1 (mos-1204, tasks 97/0, bus req 3/0); Vikunja 458 c478, 416 c479; queue-commit 21def2850 revs 2968-2976; 196 r6 gate gander-0066 2334/0, 38/38 killed; 244 granted sage-2147; 196 r6 resume granted sage-2145 after self-tee busy refusal (mos-1205). Miss: landed 222 before checking 196's manifest; 196 carried results voided. Class rule now (mos-1204, replaces the mos-1200 interface grep): before any landing push, compare every in-review and in-progress row's manifest against the landing's paths.
2026-10-21T21:12:18Z | Della (GPT-6.1-Sol, mos-1213) | Vikunja 460 independent post-v1 email plan, della/email-plan from c3d88840c | 341-line proposal, 13 post-v1 rows, six Jason questions, 42 pinned citation anchors verified. Canonical queue next della: nothing. Planning only, no live mail/service/MVP/queue change; phase B/C with Mos, no Ludwig plan read. Commit and branch push authorized by dispatch.
2026-10-21T21:37:07Z | Della (GPT-6.1-Sol, mos-1220) | Vikunja 460 phase B email cross-review, della/email-plan | Ludwig plan 4a27b73ac and review 143415779 read in full. Cross-review has ten findings and eleven merged questions; original Della plan unchanged. Canonical queue next della: nothing. Static verification only; review push followed by direct della-0002 handoff to both threads under Q210. Phase C base choice remains with Mos.
2026-10-21T21:43:02Z | Della (GPT-6.1-Sol, mos-1223) | Vikunja 460 phase C merged email plan, della/email-plan | Mos-selected base plus Ludwig grafts; original plans/reviews unchanged. Sixteen post-v1 rows, eight READY and eight WAIT/optional; eleven Jason questions. Canonical queue next della: nothing. Docs-only checks. Commit/push then immediate della-0003 handoff to Ludwig/Mos; Sage landing/queueing follows approval.
2026-10-21T21:45:20Z | Della (GPT-6.1-Sol, mos-1223 / ludwig-0004) | merged email plan focused review fixes | External content-key/expiry location and restore tests specified; fake registry work READY, production registry gated on PRD approval/Q4. Merged plan 399 lines, original plans/reviews unchanged. Commit/push and focused Ludwig review before Sage handoff.
+46 -29
View File
@@ -465,7 +465,7 @@ reports checkout or copy identity and refactor drift, and writes nothing.
```
scripts/stack-release.sh adopt BUSINESS
scripts/stack-release.sh activate ID BUSINESS [--pm | --pm-after-harness] [--migrate] [--tester-port N]
scripts/stack-release.sh activate ID BUSINESS [--pm | --pm-after-harness] [--migrate] [--tester-socket | --tester-port N (legacy copies)]
[--proof-header NAME --proof-file PATH | --no-proof | --sso] [--no-send] [--timeout S] [--plan]
scripts/stack-release.sh pm-on BUSINESS [--timeout S]
scripts/stack-release.sh rollback BUSINESS [--restore ACTIVATION [--events N]] [--timeout S] [--plan]
@@ -562,7 +562,8 @@ refuses `capture-failed` before it stops anything. Adopt uses the same rule.
The Console's row 178 proof pair gets into the pointer only through `activate`
(row 187). `--proof-header NAME --proof-file PATH` puts the pair in the
pointer's Console flags and the unit after the preview flags and before `--send`
and `--tester-port`, where `webui-service.sh` renders it, replacing any pair
and the tester flag (`--tester-socket`, or `--tester-port` for legacy copies),
where `webui-service.sh` renders it, replacing any pair
already there. `--no-proof` drops it. Either may name the live copy, which a
plain activate refuses with `already-active`, so the proof goes on or off
without changing copies; `--no-proof` with no pair in the pointer still refuses
@@ -692,6 +693,12 @@ backup's checks it prints `BACKUP_STORE <activation id> schema <version>`.
it never picks another port. `--timeout` (default 60 seconds) bounds the
gate and each stop.
A copy marked for the private tester receives `--tester-socket` during rehearsal;
a legacy copy receives no tester flag. The socket is under the rehearsal's
scratch dataRoot, at `data/console/tester.sock`. That path is longer than the live
one. A long dataRoot can pass activation's pathname preflight and still make the
rehearsal Console refuse its socket path; the rehearsal then fails closed.
Before it makes anything, it checks the pointer, the copy (present,
reachable from refactor, a readable schema, a bus host with `--no-launch`),
the port and the backup's `SHA256SUMS`. Then it makes
@@ -1264,36 +1271,46 @@ not claim an assistant answer; an uncertain receipt is never retried. Read the
current list/history before trying again. Activation still needs Mos's separate
window; changing these files does not authorize a live Console action.
### Console tester listener (`serve --tester-port N`, row 176)
### Console tester listener (`serve --tester-socket`, row 196)
`node packages/webui/src/cli.mjs serve --tester-port N` adds a second listener
bound only to `127.0.0.1:N`. N must be an integer in 1024..65535, different
from `--port` and never 7332 (the tunnel's forwarded port). It requires the
bus configuration. It calls only the tester shim; the main listener keeps its
existing human transport and rejects every `x-mosaic-tester` request.
The tester listener rejects the main preview auth header and only accepts its
own loopback Host/port. Its Settings banner identifies the tester. Its instance,
route and name editors use the tester's three writes; settings, document saves,
assistant stop, role messages and chat writes stay refused. Writes require the
exact `http://127.0.0.1:N` Origin, same-origin Fetch Metadata, JSON and the existing
bounded strict bodies. `tester-off` and `tester-required` answer HTTP 403.
`node packages/webui/src/cli.mjs serve --tester-socket` adds private HTTP at
`<dataRoot>/console/tester.sock` (0600) under console/ (0700). The path must fit
107 bytes. Unsafe ownership, modes, symlinks and pre-existing entries refuse.
The main Console remains on its configured TCP port. New copies refuse --tester-port.
`scripts/webui-service.sh render|install --tester-port N` carries the flag into
ExecStart, and status names its URL. Bad tester ports exit 4. Install remains a
unit write for the authorized operator, with no start/restart verb.
The tester's fixed Host is mosaic-tester.invalid and its Origin is
http://mosaic-tester.invalid. No DNS is used. POST also needs same-origin Fetch
Metadata and bounded JSON. The endpoint uses only TESTER_CLI. It admits the
existing instance, route, name and tester-chat writes, never owner settings,
document saves, assistant stop or role messages. bus.tester stays Jason's switch.
Authorization, login/session cookies and configured proxy headers refuse.
`stack-release.sh activate ID BUSINESS --tester-port N` replaces or adds this
Console flag in both the installed unit and the pointer. It checks the target
copy's own Console before stopping anything; an explicit unsupported request
refuses `tester-port-target`. A port outside 1024 to 65535, 7332 or the
Console's `--port` is a usage error (exit 4). Under `--plan` both are listed
with the plan's other refusals instead, the bad port as `tester-port-invalid`,
and the plan exits 3. Without an explicit flag, activate and rollback
keep it between supporting copies, or drop it when the target predates support.
The plan prints the final Console flags and any dropped `--tester-port N`;
the steps log records a drop too. `pm-on` keeps the pointer's Console flags.
These operations retain their existing Mos/Jason window authority. Nothing in
this row forwards the tester port through the tunnel or changes live units.
```sh
node packages/cli/src/tester-http.mjs GET /api/bus/agents
printf '%s' '{"name":"fixture"}' | node packages/cli/src/tester-http.mjs POST /api/chat/projects
```
The helper loads the selected instance config and reports HTTP status/text/body
as JSON, exiting 2 for refusal. It never retries writes. Direct browser access
is removed with Mos's acceptance (mos-1015). A future authenticated browser relay
behind SSO is a separate row. No TCP compatibility bridge is provided.
`webui-service.sh render|install --tester-socket` checks target support and carries
the flag into ExecStart. `stack-release.sh activate ID BUSINESS --tester-socket`
explicitly replaces an inherited --tester-port pair in both unit and pointer.
Without explicit conversion a new target refuses tester-transport-migration before
stopping anything. Unsupported targets refuse tester-socket-target. Rollback drops
unsupported socket enablement, visibly in plan/steps, without enabling TCP. Legacy
flags remain supported by the release tool for legacy target copies only. Failed
activation recovery can restore the previous TCP unit and its old limits.
Activation of a new copy preflights the pinned root-owned system Python ELF,
records its resolved path/version/identity/hash and checks the bus's fresh startup
receipt. Restarts revalidate and append actual interpreter identity to
`<dataRoot>/bus/peercred-starts.jsonl`, without comparing an old activation hash.
After an OS interpreter update, the next authorized re-pin uses a different reviewed
copy and refreshes activation evidence. Do not bypass already-active or use a PATH shim.
All live operations keep their existing window authority.
Console proxy proof (row 178, #1676): `serve`, `webui-service.sh render` and
`install` accept `--proof-header NAME --proof-file PATH` together, under preview
+2 -1
View File
@@ -61,7 +61,8 @@ it out, and the mirror's refs belong to the bus. If `HEAD` names no commit, stop
5. When Jason has a draft mapping, run it again with `--mapping`. The file holds the library entry
alone, `{"kind": "library", "remote": ..., "branch": ..., ...}`, without the business file or the
`"projects"` wrapper around it. `remote` and `branch` are required even though the survey doesn't
fetch. `mosaic` must be able to read the file. Set `vaultPrefix` to her books repo's folder name
fetch. `user` is optional here: it is the bot account name from design step 3, required for sync.
`mosaic` must be able to read the file. Set `vaultPrefix` to her books repo's folder name
in her vault. Without it, a link with the full path from the top of her vault resolves to nothing,
and the setup sheet tells her to write links that way.
@@ -0,0 +1,72 @@
# Brief: bundled Pocket ID for users with no OIDC provider (Q203, Vikunja #458), design
Sage, 2026-10-21, for Mos's order mos-1202 item 2. Vikunja #458, parent #412 (SSO). Owner Launchpad,
reviewer Webby, gate owner Sage. After row 252. This row is a design. The deliverable is
`agents/launchpad/work/queue-ROW/DESIGN.md`. Build rows come from it after the design is approved.
## What Jason asked for
DECISIONS Q203, relayed by Mos in mos-1202, verbatim: "Let's use O6. If a user does not have an OIDC
provider to configure, we will need to use a bundled Pocket ID instances to handle this traffic."
O6, as Mos put it to Jason: "keep the REQ-WEB-2 wording OIDC-only. Identity is the (issuer, subject) pair
from the Console's own verified OIDC sign-in. Local accounts come from a provider, and the Console never
holds passwords." PRD 1.9 (f48725cfc) has REQ-WEB-2 on those terms.
Mos's reading of the second sentence, labelled as his, following lead decision 44 items 5 and 6
(`docs/plans/2026-09-26_lead-decisions.md:655-663`):
- The installer offers two choices: point the Console at one or more existing OIDC providers, or deploy a
bundled Pocket ID.
- The stack installs that Pocket ID and writes it as the Console's one providers entry. No Pocket ID source
goes in the repository, and the Console gets no sign-in path apart from the Q175 relying party.
- Pocket ID is passkey-only. Agents keep per-role service tokens.
- dragon-lin keeps Authentik. This is outside the 10-27 MVP.
The PRD requirement for Q203 is drafted separately and goes to Mos first. Until it lands, cite Q203 and
this brief. The PRD's v1 scope list still has "Pocket ID and SSO" out of v1, and its technical notes say
"Pocket ID is wired in after v1". This row doesn't change either.
## Where things stand
- Row 192 (landed 4429a104f) is the Console's OIDC relying party, `--sso`, with the Q175 start checks.
Row 200 adds `stack-release.sh activate --sso`, and row 238 its `--instance` hint.
- REQ-INST-1 sets what an optional install component is: pinned, health-checked, removable, and switched
on in the organization file or system config. SearXNG is the first one.
- Researcher's note `agents/researcher/work/2026-10-04_vikunja-pocketid.md` covers Pocket ID v2.17.0
(BSD-2, passkey-only, client credentials). Check the version against what is current before pinning.
## What the design must settle
1. The choice. Where the installer asks, how "existing providers" and "bundled Pocket ID" are recorded,
and what each writes into `console.sso`. With the bundled choice, Pocket ID is the only providers
entry.
2. The component, against REQ-INST-1: the pinned release and how its digest is checked, how it runs
(unit, user, data directory under the instance's data root), its health check, and removal. No Pocket ID
source in the repository.
3. The Q175 start checks against the bundled instance. The Console must start against it with no
relaxation: discovery, PKCE S256, asymmetric signing algorithms only, issuer match, registered redirect
and post-logout URIs. Name any Pocket ID default that would fail a check, and the setting that fixes it.
4. The client. Who creates the Console's OIDC client in Pocket ID, and how its secret reaches the slot
file. No agent reads or prints the value. Secrets never enter the repository or an image.
5. The first person. Pocket ID is passkey-only, so say how the installing user enrolls a passkey, and how
their subject gets into `console.sso`. On Jason's instances only his subject goes there (mos-1197).
6. Reach. WebAuthn needs a secure origin and a fixed relying-party id. State what the bundled instance
needs (host name, TLS, proxy) and keep loopback-only the default. Anything that changes who can reach the
Console or Pocket ID from outside is Jason's, so the design names those steps as his.
7. Agents. Per-role service tokens are unchanged. Pocket ID issues nothing an agent uses.
8. Instances. Each named instance gets its own Pocket ID or shares one. Say which and why, given one
system config per instance.
9. Tests on stand-ins only: a fake provider shaped like Pocket ID (discovery, JWKS, token endpoint), as row
192's tests do. No test downloads or starts a real Pocket ID.
10. The build rows, each with its own gate, in landing order.
## Boundaries
- Design only. No code, no install anywhere, no download of Pocket ID onto any host.
- dragon-lin keeps Authentik. No change to .53 or .54 (mos-1167).
- No external accounts, no DNS or certificate requests, no Traefik change. Those are Jason's.
## Gate
Webby approves DESIGN.md. Sage checks it against Q203, O6, REQ-WEB-2 and REQ-INST-1, and sends any choice
that would change what an existing requirement allows or forbids to Mos before the build rows are added.
+341
View File
@@ -0,0 +1,341 @@
# Post-v1 email processing and monitoring plan (Della)
**Status:** proposal for Mos phase A, Vikunja 460, mos-1213. Planning only.
**Goal:** receive mail for named agents and users through Stack-owned connections,
then expose authorized reading, routing and drafting equally to every harness.
**Boundary:** every build row below is **post-v1**. None is required for the
2026-10-27 MVP. This document changes neither PRD 1.10 nor the live Stack.
No service, mailbox connection, live-mail read, account, DNS change or spend is authorized.
R21 binds the specialist: **“The email specialist reads and drafts, never sends.”**
## Evidence and reuse
Citations are pinned, not claims about deployed behavior. Prefixes below identify
the repository and commit for every `file:line` citation. Both prior clones were
inspected read-only. No prior application or test suite was executed.
| Prefix | Pinned evidence |
| --- | --- |
| S | Stack `c3d88840c5b6ffa62d2a6a4aa4385d388234d777`, this worktree |
| MP | `mail-processor` `95b02989f4d4276dded00c90ffd74ebb7d03ae8b` |
| EP | `email-processor` `7a6492190cd376058e29a04fd0593e86bae1a8e5` |
| SM | `origin/huey/system-manager-design` at `2ee68cd69337bb933a8fabeb04bcd71835170171`, Q198 r6, superseding r5 `fd44f7c60` |
**F1 Existing mail code:** recursive comparison found identical `src/` trees in MP
and EP. Reuse once, not as two running products. Both implement normalized messages
and a connector boundary (`src/email_processor/connectors/base.py:8,30`), M365
pagination/rate-limit handling (`connectors/m365.py:81,138`, under that same source
directory), ordered AND/OR rules (`src/email_processor/rule_engine.py:81,124`), and
bounded preview/cold-start safety (`src/email_processor/poller.py:101,122,152`).
Port these concepts and selected synthetic tests into Stack-native modules after
dependency/license review. Neither copy proves IMAP or POP3 support: connector
selection handles only M365 (`src/email_processor/poller.py:48`).
**F2 Prior intent:** EP `docs/PRD.md:25,39` describes a single-organization M365
application, not the Stack add-on. MP `docs/PRD.md:26,66,204,235` specifies generic
Graph/IMAP/POP3 drivers, separated intake/execution, fail-closed activation and
atomic staging/checkpoints. Those are useful design requirements, not implemented
features. Adopt the safety properties and conformance cases, not its independent
tenant/profile product, bootstrap authority or mandatory standalone web setup.
**F3 Drop unsafe coupling:** in both repos, `src/email_processor/poller.py:186,200,301`
checks for duplicates before effects and advances a timestamp to wall-clock now.
`src/email_processor/models.py:266` has indexes, not a unique mailbox/message key.
Replace these with durable provider cursors, unique identities and transactional
delivery. Drop connector caches keyed just by mailbox name (`poller.py:48`), direct
GLPI/ntfy effects (`action_executor.py:30,69,159`) and source category writes
(`connectors/m365.py:170`, `poller.py:359`). Drop direct model calls and content logs
(`classifiers/llm.py:79,105,109,123`), replacing decisions through Q207.
Do not import deployment manifests, credential settings, existing accounts or data.
**F4 Stack enforcement:** S `packages/harness/src/tools.mjs:1` already maps typed
tools through the runner socket with broker checks. Its MCP bridge carries no
capability (`packages/harness/src/mcp-server.mjs:1,34`). Reuse that path for all
harnesses, rather than giving each one mailbox credentials or a monitoring plugin.
S `packages/bus/src/broker.mjs:690,701,1601,1666` supplies human-approved grant
consumption and action authority. `packages/bus/src/credentials.mjs:142,358,379`
supplies scoped references and protected use. Mail kinds and grants do **not** exist:
S `packages/business/src/vocabulary.mjs:109,119` accepts Gitea, Vikunja and host kinds.
Mail requires reviewed extensions, not relabelling mail credentials as an existing kind.
**F5 Product constraints:** S `docs/prd/mosaic-stack.md:432,454,477,496` defines
chief-raised grants, exact human approval, one-time consumption and immediate local
revocation. Its `:507,542,582,607` covers decision evidence, thread binding and hard
waits. PRD “inbox” is the decision inbox, not email. Mail needs a separately approved
post-v1 PRD addendum. S `packages/plugins/src/kinds.mjs:9,32` has no mail-source kind.
Follow REQ-PLUG-1/2 (S PRD `:876,910`) for provider adapters.
**F6 Monitoring precedent:** S `tools/agent-watch/jarvis-email-watch-liveness.sh:8,26,53`
checks timer state, failed execution and staleness. Reuse the distinction between
alive, failed and unknown, not its legacy unit names or fleet wiring. SM
`docs/plans/2026-10-21_q198-system-manager.md:262,291,304,326` supplies protected mode,
independent probes and controlled resume. It is a design dependency, not shipped proof.
## Architecture and sources
**D1 One owner per instance.** Add an optional supervised `packages/mail/` service,
with a single leased owner per configured source. It owns connection pools, OAuth
refresh, scheduling, cursors and intake. A harness can stop, restart or change without
losing monitoring. Multiple accounts are isolated by instance/business/source ID.
“One service” does not mean one global credential, mailbox or shared authorization.
Default installation is disabled and opens no connection.
Use Node modules aligned with the Stack, adapting the Python design rather than
embedding both applications and their separate databases/authentication/UI. A Python
sidecar would reuse more syntax but duplicate operational boundaries. Harness-native
connectors would repeat connection state and fail Jason's cross-harness requirement.
**D2 Source contract.** Propose a reviewed `mail-source` plugin kind, list-selected,
with `descriptor()`, `scan({source,cursor,limits,signal})`,
`fetch({source,item,part,limits,signal})` and `health({source,signal})` exports.
`scan` returns bounded observations, continuation, terminal cursor and identity epoch.
Adapters receive protected credential-use callbacks, never agent-provided secret paths.
Core owns grants, leases, persistence, rule evaluation and audit. No adapter sends,
deletes, labels, moves or marks messages read. Use the existing channel kind only
where its dispatch contract fits presentation, not as a substitute polling interface.
| Source | Proposed implementation and boundary |
| --- | --- |
| Microsoft 365 | Graph read-only access, per-folder delta/continuations and immutable message IDs. Replace timestamp polling. Restrict application rights to named mailboxes and verify effective scope, including any additive Entra permissions. |
| IMAP | TLS with certificate verification, OAuth where supported, otherwise a separately approved service/app credential. Read-only selection and `BODY.PEEK`; UIDVALIDITY plus UID, never sequence number. IDLE is a wake-up hint with periodic reconciliation. |
| Other providers | Prefer a conforming IMAP source initially. Gmail API and JMAP are later adapters only when a named need justifies them. No new provider account is implied. |
| POP3 | Deferred optional row. Require stable UIDL, TLS and read-only retrieval. No `DELE`. Reject automatic activation without safe identity/reconciliation. |
Graph delta is folder scoped and cursors are opaque ([Microsoft delta documentation](https://learn.microsoft.com/en-us/graph/delta-query-messages)).
Immutable IDs survive moves within a mailbox, not archive-mailbox moves or reimports
([Microsoft ID documentation](https://learn.microsoft.com/en-us/graph/outlook-immutable-id)).
Mailbox-scoped Application RBAC and unscoped Entra grants are independent
([Microsoft RBAC documentation](https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac)).
IMAP identity, read-only selection and PEEK semantics follow [RFC 9051](https://www.rfc-editor.org/rfc/rfc9051.html).
These are protocol references checked 2026-10-21, not evidence of installed support.
**D3 Addressing.** Version a route table mapping trusted delivery evidence to a stable
Stack user or role-instance ID, never a display name or a newly invented role.
Aliases on an approved ingress mailbox are the recommended inexpensive start.
Plus-addresses are acceptable only after fixtures and a later authorized provider test
prove the exact recipient survives delivery. They are not a security boundary.
Dedicated mailboxes provide stronger provider-side separation for confidential users
or agents, at the cost of account management and possible licenses.
Bind routes to source identity plus provider-controlled original-recipient evidence.
`To`/`Cc`, forwarded headers and arbitrary `Delivered-To` headers are insufficient
unless the enrolled delivery path establishes their provenance. Bcc or ambiguous
alias delivery goes to restricted operator review, never guessed routing. Unknown
addresses do not create agents. Multiple recipients produce independently authorized
deliveries without revealing other recipients' mail. Jason approves domains, names,
aliases, mailboxes and each provider-side change before provisioning. No SMTP/MX
server, catch-all domain or public webhook is proposed for the initial increment.
## Processing, data and retention
**D4 Intake pipeline:** approved source → bounded discovery → bounded parse → durable
observation/dedup → versioned deterministic rules → authorized delivery outbox →
Stack notification/work request. Provider cursor advancement and durable observations
commit together under the source lease/generation. Outbox delivery has a stable key
and a recipient-side unique receipt, so a crash after delivery but before acknowledgment
does not create a second task/message. Never promise exactly-once external effects.
Default candidates: poll every 60 seconds, at most 50 observations and 10 MiB fetched
per slice, fair scheduling between sources. Persist continuation when the slice fills.
Cap each source at 1,000 pending deliveries and 100 MiB of retained content initially.
Persist quota accounting across restarts and slices. Reaching a quota holds new intake
and raises an alert, while read-only health checks continue.
First activation establishes a from-now baseline without actions on old mail. Historical
import is a separate approved time/count window, previewed before activation. Cap
violations, disk-full and unavailable audit storage hold work without advancing past
unstaged mail. These limits are proposed settings to validate, not measured capacities.
Provider identity is scoped to business/source/account: Graph immutable ID, IMAP
folder/UIDVALIDITY/UID or POP3 UIDL. RFC Message-ID, thread ID and content similarity
are secondary correlation signals, never sole proof of uniqueness. Preserve distinct
messages with missing or reused Message-ID. An IMAP move, epoch reset, expired delta
cursor, archive move or reimport enters bounded reconciliation. Record aliases only
with sufficient identity evidence. Otherwise hold possible duplicate actions for review.
An edit is a new observation version, not automatic permission to rerun prior actions.
Preserve dedup tombstones beyond content expiry. Replay needs a new authorized intent,
parent link and current grants. A rule edit never retroactively executes preview work.
**D5 Parsing and rules.** Preserve provenance for normalized sender, recipients,
timestamp, subject, text, thread references and attachment descriptors. Bound MIME
depth to 10, parts to 100 and normalized text to 256 KiB. Decode safely, flag malformed
or truncated content and quarantine failures. Prefer text, sanitize HTML for display,
disable remote images/URLs and never execute active content. Rules use finite allowlisted
fields/operators, stable priority and first-match by default. Reject executable rules
and unbounded regex. Record rule/version and routing reason. Unmatched mail is held
for its authorized owner, not dropped or copied fleet-wide. Routing a user notification
does not launch an agent. Agent wake-up is a separate admission-controlled work request.
Optional classification and any mail-triggered decision go through the Stack's one
decision module under DECISIONS Q207. The mail service neither selects nor falls back
to a model locally. Core policy still determines permitted actions and human hard waits.
Use minimal, explicitly permitted content and an allowlisted result schema. A model
result is advisory data, never a grant. Missing model service leaves deterministic
intake working and model-dependent work held. Summaries/drafts run as ordinary governed
agent work, not an extra mail-service model client.
**D6 Attachments.** Initially enumerate metadata only. An authorized explicit fetch
quarantines bytes under an opaque ID, with a 10 MiB per-part and per-message total cap.
Never trust filenames as paths, unpack archives, execute macros or follow embedded
links. Content extraction requires isolated no-network processing, resource bounds,
scanner success and a separate read right. Failed, encrypted or unsupported files stay
quarantined. A clean scan does not make attachment instructions trustworthy.
**D7 Storage.** Proposed `<dataRoot>/mail/<business>/` contains encrypted content blobs,
SQLite observation/outbox state, encrypted cursors, local drafts and append-only event evidence.
No mail content enters git or images. Service-only directories are 0700, files 0600.
Use scoped keyed fingerprints for private dedup fields. Separate content keys from
provider slots. Keep tokens, delta URLs and authentication errors out of ordinary logs.
Default durable records hold opaque IDs, state, versions and counts, not bodies,
subjects, recipient lists or attachment names. Content access is explicit and audited.
Recommended content policy for an enabled pilot: encrypted bodies and local drafts
for 30 days, explicitly fetched attachments for 7 days, minimal metadata/tombstones for
365 days. Without Jason's approved content/retention policy, persist metadata only and
show that later content fetch requires the source online. Expired content returns an
explicit unavailable result. Define backup/key expiry and restore suppression together:
restoring an old backup must not resurrect expired content or redeliver old work.
Local pruning needs the Stack's `data.delete` approval path or an explicitly approved
future retention authority. Do not silently grant automatic deletion. Write receipts,
preserve legal holds and do not rewrite `<dataRoot>/runs/` or existing audit evidence.
## Access and security
**D8 Identical access contract.** Proposed tools are `mail_list(source?,cursor?,limit)`,
`mail_get(id,view)`, `mail_attachment_get(id,part)`, `mail_route(id,target,expect)`,
`mail_ack(id,expect)`, `mail_draft_create(id,text)` and `mail_health(source?)`.
All map to typed broker verbs via the existing tool socket/MCP bridge. Local CLI and
authenticated Console use the same handlers. List is paginated (default 20, max 100).
`ack` changes Stack-local state only. Drafts are local unsent records, not Graph drafts.
Health returns only authorized source metadata. No arbitrary URL, shell, source secret
path, provider query language or send tool is accepted.
Separate rights for metadata, body, attachment, draft and routing per business,
source, address binding and named principal. Recheck current role/grant on every read,
notification and action, including caches and derived summaries. Revoke immediately
fences new uses, invalidates fetch handles and cancels owned in-flight reads before
returning content. Agents receive only mail explicitly routed to their grant scope.
User mail requires that user's or explicitly delegated access. An email specialist is
not implicitly allowed to read every user's mail. Cross-business IDs return no data.
New mail access grants follow the existing chief → `credential.mint` → authenticated
human Approve chain, with exact target, rights and expiry bound once. Add the mail
grant method and action routing only through a Jason-approved PRD version and reviewed
vocabulary/role changes. Until then new actions remain hard waits. Within-role mail
access can become routine only inside those approved grants. Manual-mode actions still
need per-use approval. A hand-edited grant or approving email creates no authority.
**D9 Credentials and hostile input.** Distinguish a source service principal's
provider connection grant from an agent's mediated mail-access grant. Recommend
dedicated read-only ingress credentials per source/business, held by protected Stack
code and never shared with agents. This system-service ownership is a new approved
extension, not an exception silently taken to REQ-CRED-1. Configuration names only
an approved absolute slot path, for example
`<dataRoot>/credentials/mail/<business>/<source>/oauth`; no value or partial value is
read by a planner, returned through tools or embedded in a plan. Missing/expired/revoked
slots fail closed. Validate owned non-symlink files, rotation and revocation behavior.
Treat mail, headers, filenames, OCR and quoted replies as untrusted external data.
Sender addresses, even authenticated messages, cannot approve decisions, change policy,
mint grants or impersonate the CLI/Console human. Delimit model input and strip tool
authority, but rely on broker denial, sandboxing and least privilege for enforcement.
An email asking an agent to fetch secrets, forward documents or run commands must not
gain those rights. External action proposals use existing approval paths and provenance.
No automatic link fetches. Provider endpoints are enrolled allowlists with TLS and
redirect checks, not values selected from mail content. Logs use reason codes and IDs.
**D10 Sending.** Out of scope: SMTP submission, Graph send, forwarding, auto-replies,
DSNs generated by this service and emailed digests. Block them in adapter interfaces,
credentials and network policy. R21 stays intact even if a future separate sender role
is approved. Human export of an unsent draft is not authorization for Stack delivery.
## Monitoring and system-manager integration
**D11 Health:** report desired/actual connection state, last successful scan and durable
cursor commit, lag, backlog, lease owner, retry-after, credential status, quarantine,
outbox age and disk pressure. Never report “healthy” merely because the process lives.
Candidate stale threshold: three missed scan intervals. Alert once per incident, on
severity change and recovery through existing internal notifications/Console, not mail.
Show alert delivery failure locally. Alerts include IDs and reason codes, no content.
Register dependency scopes `mail-source/<business>/<source>` with Q198's manager.
Network/provider outages pause only affected intake and dependent fetch/dispatch.
Independent sources and authorized stored-mail reads can continue. Disk/audit failure
halts new intake/effects. Auth/config denial stays held for correction, never retries
under another credential. Malformed messages quarantine individually, not as outages.
Apply Q205–Q207 through protected mode, not the human kill switch: mechanical source
probes run independently at 60, 120, 180 seconds, increasing by 60 to a 600-second
ceiling, indefinitely, while respecting longer Retry-After. No JEV/model availability
gate and no model call for a mail health check. Mail health proves TLS/auth and scoped
source access without fetching message bodies. One lease owns retries, so adapter and
manager loops cannot multiply them. Disabled sources make no probes.
On recovery reconcile cursors/uncertain delivery receipts, pass one synthetic pipeline
canary, then resume eligible work in manager-controlled batches without approval per
session. Never send test mail or use a live message as a synthetic canary. Uncertain
external effects stay fenced. Human kill remains human-only to clear. These mail
extensions to SM D13–D15 need contract review and tests before operational reliance.
## Ordered build rows (all post-v1, not current queue edits)
Each row is a separately reviewable deliverable. Write failing synthetic cases first,
implement only that row, then run its named tests and affected existing suites. Commands
below name proposed tests. They are not tests run during this planning assignment.
Shared touch points include `packages/{bus,business,harness,plugins,cli,webui}/`.
No rollout uses an implementation candidate until normal review and re-pin gates pass.
| Row | Deliverable / files | Acceptance evidence |
| --- | --- | --- |
| E01 **post-v1** | PRD addendum and source/data schemas, `docs/prd/`, `packages/mail/schemas/`. Resolve Q1–Q6 before activating corresponding behavior. | Schema fixtures reject unknown fields, missing scopes and secret values. Disabled add-on makes zero connections and v1 startup works without it. Obtain Jason's policy approval, not implementation's implied consent. |
| E02 **post-v1** | Protected source slots and mail-access grants, `packages/bus/`, `packages/business/`. Build D8/D9 on existing grant executor. Depends E01. | `node --test packages/bus/tests/mail-grants.test.mjs`: deny non-chief raises, nonhuman approvals, altered targets, duplicate consumption, hand grants and revoked/in-flight access. Secret sentinel absent from all outputs. Existing grant/approval suites pass. |
| E03 **post-v1** | Durable intake ledger, leases, outbox and disabled supervisor, `packages/mail/src/{store,intake,service}.mjs`. Depends E02. | `node --test packages/mail/tests/intake.test.mjs`: crash at each transaction/outbox boundary, concurrent owners, disk-full, cap exhaustion and restart. No skipped cursor or duplicate recipient effect, no source calls while disabled. |
| E04 **post-v1** | Bounded MIME parsing and quarantine, `packages/mail/src/{parse,attachments}.mjs`. Depends E03. | `node --test packages/mail/tests/parse.test.mjs`: malformed MIME, oversized/deep parts, traversal filenames, remote HTML, archives and encrypted attachments. Limits enforced before expansion, zero execution/network, explicit truncation/quarantine. |
| E05 **post-v1** | M365 driver and first `mail-source` kind, `packages/plugins/src/kinds.mjs`, `packages/plugins/mail-source/m365/`. Depends E03/E04. | `node --test packages/mail/tests/m365.test.mjs`: fake Graph pagination/delta, immutable ID moves, cursor expiry, token expiry, 429, restarts and scoped-denial cases. Read-only request allowlist, no skipped/repeated delivery. Existing plugin tests pass. |
| E06 **post-v1** | IMAP driver, `packages/plugins/mail-source/imap/`. Depends E05 contract. | `node --test packages/mail/tests/imap.test.mjs`: fake TLS/IMAP server, certificate denial, IDLE disconnect, sequence renumbering, UIDVALIDITY reset, folder moves and missed wake-ups. No Seen/STORE/EXPUNGE, uncertain identities hold effects. |
| E07 **post-v1** | Address table, bounded rules and delivery, `packages/mail/src/{addresses,rules,route}.mjs`. Depends E03–E06. | `node --test packages/mail/tests/routing.test.mjs`: aliases, plus tags, Bcc, forged recipient headers, retired agents, multiple recipients, missing/reused Message-ID and duplicate provider pages. No cross-principal disclosure, no duplicate work, no authority from sender. |
| E08 **post-v1** | Broker verbs, MCP/typed tools and local drafts, `packages/bus/`, `packages/harness/`, `packages/mail/src/access.mjs`. Depends E02/E07. | `node --test packages/harness/tests/mail-tools.test.mjs`: identical schema/results/refusals through every supported harness adapter, read/attachment/draft rights separated, manual mode and expiry enforced, send impossible. Existing MCP/tools/approval suites pass. |
| E09 **post-v1** | Decision-module integration, `packages/mail/src/decisions.mjs`, central decision interface. Depends E08 and approved Q198/Q207 interface. | `node --test packages/mail/tests/decisions.test.mjs`: model outage leaves rules active, one central fallback path, schema-invalid suggestions held, no mail-local model client, hostile mail cannot approve/grant/spend/send. Minimal-context privacy and budget checks pass. |
| E10 **post-v1** | Health, alerts and protected-mode recovery, `packages/mail/src/health.mjs`, Q198 manager adapter. Depends E03/E08 and implemented/verified SM D13–D15. | `node --test packages/mail/tests/outage.test.mjs`: fake clock/provider verifies outage scope, full cooldown sequence, Retry-After, parallel probes, no model probe, no outage DLQ, restart continuity, canary/batched resume, uncertain delivery fencing and human kill persistence. |
| E11 **post-v1** | Authorized Console/CLI mail views, retention/export/prune/restore, `packages/{mail,cli,webui}/`. Depends E08/E10 and Q4 authority. | Focused `mail-retention` and `mail-ui` tests prove ACLs on lists/search/export, expired-content denial, exact delete approval, holds, backup-expiry restore suppression and no old-work replay. Decision inbox remains distinct, alerts survive mail outage. |
| E12 **post-v1** | Opt-in operator runbook and acceptance packet under `docs/`. Depends E01–E11. | Offline end-to-end synthetic message → parse → route → authorized tools → local draft, under every supported harness. Demonstrate revoke, outage recovery and disable/rollback preserving evidence. Live pilot is a separate Jason-authorized step after named address/account/scope/retention approvals. No external mail sent. |
| E13 **post-v1 optional** | POP3 adapter, `packages/plugins/mail-source/pop3/`, only for an approved named need. Depends E12. | Fake server tests UIDL absence/reuse, restart and TLS failure, retrieval caps, reconciliation and zero DELE. Unsupported identity refuses activation. Other adapters remain unaffected. |
## Questions for Jason (recommendations, not assumed approvals)
1. **Q1 Source and address pilot:** which provider, domain and existing or new mailbox
should receive agent/user mail? **Recommend** one separately approved read-only
ingress mailbox, aliases for named agent/user recipients, no catch-all or MX change.
Use dedicated mailboxes for confidential separation. Prove preserved delivery
identity before enabling alias/plus routing. New addresses/accounts wait for Jason.
2. **Q2 Grant ownership:** approve a protected source service principal plus separately
scoped per-agent/user mail-access grants? **Recommend yes**, through a post-v1 PRD
amendment and existing chief/human approval model. No agent gets provider secrets
or implicit access to personal mail. Enable only the explicitly requested sources.
3. **Q3 Automation:** may rules route mail and wake a granted agent without per-message
human approval? **Recommend yes** for reviewed within-role routes, subject to manager
admission and manual mode. Reading/drafting does not authorize requested external
effects. Any model-needed decision uses the one Q207 module.
4. **Q4 Content and deletion:** approve the 30-day body/draft, 7-day attachment and
365-day minimal-evidence policy, including encrypted backups and deletion authority?
**Recommend these pilot limits**, metadata-only until approved, with an exact
`data.delete` approval binding for pruning. Do not weaken the hard-waits list.
Jason must identify any business retention/hold requirements before live intake.
5. **Q5 Exposure and costs:** allow provider webhooks or paid mailbox infrastructure?
**Recommend no initially**. Use outbound Graph polling/IMAP, existing internal
notifications and no new exposure. Each future webhook, DNS/MX change or spend
gets its own explicit authorization.
6. **Q6 Sending:** should any future role send email? **Recommend keep sending out
of this add-on**. Retain R21 permanently for the specialist. Any later sender is a
separate design with exact-recipient approval, scoped credentials and effect evidence,
not a toggle that silently turns drafts into delivery.
## Planning receipt and handoff
Verified pinned commits, source-tree equality, cited code boundaries and ten requested
coverage areas. No implementation, dependency installation, legacy test execution,
mail connection or live-stack change occurred. Canonical `queue next della` returned
`nothing` after the linked-worktree refusal. No queue/MVP row changed. Vikunja 460 is
the supplied tracking task. Mos/Sage owns any required Gitea issue and future queue
enrollment because this seat has no Gitea API credential. Phase B review and phase C
synthesis remain with Mos. Della has not read Ludwig's plan. This branch push delivers
the planning artifact only, not approval to build or deploy it.
+399
View File
@@ -0,0 +1,399 @@
# Post-v1 email processing and monitoring plan
**Status:** phase C candidate, mos-1223, Vikunja 460. Ludwig reviews before Sage lands it.
**Goal:** Stack-owned mail intake, monitoring, authorized reading/routing and local drafting for every harness.
**Boundary:** all rows post-v1, none required for 2026-10-27. No PRD 1.10 or live change here.
Q210 ready rows go to non-v1 seats after handoff. No ready label authorizes live connections,
addresses/accounts, DNS/MX, exposure, spend or external mail. Original plans/reviews stay unchanged.
R21: **“The email specialist reads and drafts, never sends.”**
## Evidence and reuse
Citations are pinned, not claims about deployed behavior. Prefixes below identify the repository and commit
for every `file:line` citation. Both prior clones were inspected read-only. No prior application or test suite
was executed.
| Prefix | Pinned evidence |
| --- | --- |
| S | Stack `c3d88840c5b6ffa62d2a6a4aa4385d388234d777`, this worktree |
| MP | `mail-processor` `95b02989f4d4276dded00c90ffd74ebb7d03ae8b` |
| EP | `email-processor` `7a6492190cd376058e29a04fd0593e86bae1a8e5` |
| SM | `origin/huey/system-manager-design` at `2ee68cd69337bb933a8fabeb04bcd71835170171`, Q198 r6, superseding r5 `fd44f7c60` |
**F1 Existing mail code:** recursive comparison found identical `src/` trees in MP and EP, including identical
tests. MP contains EP plus three commits. Reuse once. Both implement normalized messages and a connector
boundary (`src/email_processor/connectors/base.py:8,30`), M365 pagination/rate-limit handling
(`connectors/m365.py:81,138`, under that same source directory), ordered AND/OR rules
(`src/email_processor/rule_engine.py:81,124`), and bounded preview/cold-start safety
(`src/email_processor/poller.py:101,122,152`). Port all 36 rule-engine and 15 predeployment safety test cases
as synthetic JS cases after dependency/license review. Remove subject logging (`rule_engine.py:147`). Neither
copy proves IMAP or POP3 support: connector selection handles only M365 (`src/email_processor/poller.py:48`).
**F2 Prior intent:** EP `docs/PRD.md:25,39` describes a single-organization M365 application, not the Stack
add-on. MP `docs/PRD.md:26,66,204,235` specifies generic Graph/IMAP/POP3 drivers, separated intake/execution,
fail-closed activation and atomic staging/checkpoints. Those are useful design requirements, not implemented
features. Deployment manifests target USC, not proof of a currently running service. Do not enroll an employer
tenant by default or change any existing service. Adopt the safety properties and conformance cases, not its
independent tenant/profile product, bootstrap authority or mandatory standalone web setup.
**F3 Drop unsafe coupling:** in both repos, `src/email_processor/poller.py:187-195,200,301` checks for
duplicates before effects and advances a timestamp to wall-clock now. `src/email_processor/models.py:266` has
indexes, not a unique mailbox/message key. Replace these with durable provider cursors, unique identities and
transactional delivery. Drop connector caches keyed just by mailbox name (`poller.py:48`), direct GLPI/ntfy
effects (`action_executor.py:30,69,159`) and source category writes (`connectors/m365.py:170`,
`poller.py:359`). Drop direct model calls and content logs (`classifiers/llm.py:79,105,109,123`), replacing
decisions through Q207. Do not import deployment manifests, credential settings, existing accounts or data.
Graph already selects `body` and normalizes HTML (`connectors/m365.py:27,129`), although `body_text` is empty.
**F4 Stack enforcement:** S `packages/harness/src/tools.mjs:1` already maps typed tools through the runner
socket with broker checks. Its MCP bridge carries no capability (`packages/harness/src/mcp-server.mjs:1,34`).
Reuse that path for all harnesses, rather than giving each one mailbox credentials or a monitoring plugin. S
`packages/bus/src/broker.mjs:690,701,1601,1666` supplies human-approved grant consumption and action
authority. `packages/bus/src/credentials.mjs:142,358,379` supplies scoped references and protected use. Mail
kinds and grants do **not** exist: S `packages/business/src/vocabulary.mjs:109,119` accepts Gitea, Vikunja and
host kinds. Mail requires reviewed extensions, not relabelling mail credentials as an existing kind.
**F5 Product constraints:** S `docs/prd/mosaic-stack.md:432,454,477,496` defines chief-raised grants, exact
human approval, one-time consumption and immediate local revocation. Its `:507,542,582,607` covers decision
evidence, thread binding and hard waits. PRD “inbox” is the decision inbox, not email. Mail needs a separately
approved post-v1 PRD addendum. S `packages/plugins/src/kinds.mjs:9,32` has no mail-source kind. Follow
REQ-PLUG-1/2 (S PRD `:876,910`) for provider adapters.
**F6 Monitoring precedent:** S `tools/agent-watch/jarvis-email-watch-liveness.sh:8,26,53` checks timer state,
failed execution and staleness. Reuse the distinction between alive, failed and unknown, not its legacy unit
names or fleet wiring. SM `docs/plans/2026-10-21_q198-system-manager.md:262,291,304,326` supplies protected
mode, independent probes and controlled resume. It is a design dependency, not shipped proof. S
`packages/discord/src/connector.mjs:1-12` and `journal.mjs:60,71` provide authorize → journal → engine →
deliver and intent/result reconciliation precedents. Reuse principles, not Discord nonce assumptions or its
old approval-button path for mail authorization.
**F7 Secret-scan evidence:** Ludwig reports a pattern-based scan of both prior trees and full histories with
no candidate credential value found (his plan `4a27b73ac`, `docs/plans/2026-10-21_email-plan-ludwig.md:35`).
Della did not independently repeat that scan. It proves neither absence of secrets nor that rotation is
unnecessary. Any later finding is reported by path and commit only, never by value or partial value.
## Architecture and sources
**D1 One owner per instance.** Add an optional supervised `packages/mail/` service, with a single leased owner
per configured source. It owns connection pools, OAuth refresh, scheduling, cursors and intake. A harness can
stop, restart or change without losing monitoring. Multiple accounts are isolated by instance/business/source
ID. “One service” does not mean one global credential, mailbox or shared authorization. Default installation
is disabled and opens no connection.
Use Node modules aligned with the Stack, adapting the Python design rather than embedding both applications
and their separate databases/authentication/UI. A Python sidecar would reuse more syntax but duplicate
operational boundaries. Harness-native connectors would repeat connection state and fail Jason's cross-harness
requirement.
**D2 Source contract.** Propose a reviewed `mail-source` plugin kind, list-selected, with `descriptor()`,
`scan({source,cursor,limits,signal})`, `fetch({source,item,part,limits,signal})` and `health({source,signal})`
exports. `scan` returns bounded observations, continuation, terminal cursor and identity epoch. Adapters
receive protected credential-use callbacks, never agent-provided secret paths. Core owns grants, leases,
persistence, rule evaluation and audit. No adapter sends, deletes, labels, moves or marks messages read. Use
the existing channel kind only where its dispatch contract fits presentation, not as a substitute polling
interface.
| Source | Proposed implementation and boundary |
| --- | --- |
| Microsoft 365 | Graph `Mail.Read`, read-only access, per-folder delta/continuations and immutable message IDs. Replace timestamp polling. Restrict application rights to named mailboxes and verify effective scope, including any additive Entra permissions. |
| IMAP | TLS with certificate verification, OAuth where supported, otherwise a separately approved service/app credential. Read-only selection and `BODY.PEEK`; UIDVALIDITY plus UID, never sequence number. IDLE is a wake-up hint with periodic reconciliation. |
| Other providers | Prefer a conforming IMAP source initially. Gmail API and JMAP are later adapters only when a named need justifies them. No new provider account is implied. |
| POP3 | No build row until Jason names a POP-only need. A future adapter requires stable UIDL, TLS, read-only retrieval and safe identity/reconciliation. No `DELE`. |
Graph delta is folder scoped and cursors are opaque ([Microsoft delta
documentation](https://learn.microsoft.com/en-us/graph/delta-query-messages)). Immutable IDs survive moves
within a mailbox, not archive-mailbox moves or reimports ([Microsoft ID
documentation](https://learn.microsoft.com/en-us/graph/outlook-immutable-id)). Mailbox-scoped Application RBAC
and unscoped Entra grants are independent ([Microsoft RBAC
documentation](https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac)). IMAP identity,
read-only selection and PEEK semantics follow [RFC 9051](https://www.rfc-editor.org/rfc/rfc9051.html). These
are protocol references checked 2026-10-21, not evidence of installed support.
**D3 Addressing.** Version a route table mapping trusted delivery evidence to a stable Stack user or
role-instance ID, never a display name or a newly invented role. The org file holds this human-approved table
through its versioned audited write path. No agent reroutes messages or widens its own route. A route grants
no read right. The first source follows Q1’s provider. Offline adapters need not wait for that choice. Review
maintained exact-pinned IMAP/MIME dependencies in their owning rows, initially Ludwig’s
`imapflow`/`mailparser` candidates. Do not write either protocol/parser in-house. Aliases on an approved
ingress mailbox are the recommended inexpensive start. Plus-addresses are acceptable only after fixtures and a
later authorized provider test prove the exact recipient survives delivery. They are not a security boundary.
Dedicated mailboxes provide stronger provider-side separation for confidential users or agents, at the cost of
account management and possible licenses.
Bind routes to source identity plus provider-controlled original-recipient evidence. `To`/`Cc`, forwarded
headers and arbitrary `Delivered-To` headers are insufficient unless the enrolled delivery path establishes
their provenance. Bcc or ambiguous alias delivery goes to restricted operator review, never guessed routing.
Unknown addresses do not create agents. Multiple recipients produce independently authorized deliveries
without revealing other recipients' mail. Jason approves domains, names, aliases, mailboxes and each
provider-side change before provisioning. No SMTP/MX server, catch-all domain or public webhook is proposed
for the initial increment.
## Processing, data and retention
**D4 Intake pipeline:** approved source → bounded discovery → bounded parse → durable observation/dedup →
versioned deterministic rules → authorized delivery outbox → Stack notification/work request. Provider cursor
advancement and durable observations commit together under the source lease/generation. Outbox delivery has a
stable key and a recipient-side unique receipt, so a crash after delivery but before acknowledgment does not
create a second task/message. Never promise exactly-once external effects.
Default candidates: poll every 60 seconds, at most 50 observations and 10 MiB fetched per slice, fair
scheduling between sources. Persist continuation when the slice fills. Cap each source at 1,000 pending
deliveries and 100 MiB of retained content initially. Persist quota accounting across restarts and slices.
Reaching a quota holds new intake and raises an alert, while read-only health checks continue. First
activation establishes a from-now baseline without actions on old mail. Historical import is a separate
approved time/count window, previewed before activation. Cap violations, disk-full and unavailable audit
storage hold work without advancing past unstaged mail. These limits are proposed settings to validate, not
measured capacities.
Provider identity is scoped to business/source/account: Graph immutable ID, IMAP folder/UIDVALIDITY/UID or
POP3 UIDL. RFC Message-ID, thread ID and content similarity are secondary correlation signals, never sole
proof of uniqueness. Preserve distinct messages with missing or reused Message-ID. An IMAP move, epoch reset,
expired delta cursor, archive move or reimport enters bounded reconciliation. Record aliases only with
sufficient identity evidence. Otherwise hold possible duplicate actions for review. An edit is a new
observation version, not automatic permission to rerun prior actions. Preserve dedup tombstones beyond content
expiry. Replay needs a new authorized intent, parent link and current grants. A rule edit never retroactively
executes preview work.
**D5 Parsing and rules.** Preserve provenance for normalized sender, recipients, timestamp, subject, text,
thread references and attachment descriptors. Bound MIME depth to 10, parts to 100 and normalized text to 256
KiB. Decode safely, flag malformed or truncated content and quarantine failures. Prefer text, sanitize HTML
for display, disable remote images/URLs and never execute active content. Rules use finite allowlisted
fields/operators, stable priority and first-match by default. Reject executable rules and unbounded regex.
Record rule/version and routing reason. Unmatched mail is held for its authorized owner, not dropped or copied
fleet-wide. Routing a user notification does not launch an agent. Agent wake-up is a separate Q198-admitted
work request. Wake requires an approved route, current access grant, sender allowlist and trusted aligned
DMARC authentication. Strip/ignore sender-supplied Authentication-Results. Ambiguous forwarded/list mail or
failed authentication goes to restricted review.
Optional classification and any mail-triggered decision go through the Stack's one decision module under
DECISIONS Q207. The mail service neither selects nor falls back to a model locally. Core policy still
determines permitted actions and human hard waits. Use a reviewed mail decision schema inside SM D12’s 4 KiB
request limit (`:503-512`). Initial input is metadata only: opaque eligible route IDs, work/capability/error
enums and bounded numeric state. Sender domain/auth fields or any new mail-derived field need explicit schema
approval and redaction tests; no free-form rule summaries or body. If metadata cannot decide, hold/unknown
rather than invent confidence. Body-based summaries and drafts are separate governed work, not mail-local
routing decisions. A model result is advisory data, never a grant. Missing model service leaves deterministic
intake working and model-dependent work held. Summaries/drafts run as ordinary governed agent work, not an
extra mail-service model client.
**D6 Attachments.** Initially enumerate metadata only. An authorized explicit fetch quarantines bytes under an
opaque ID, with a 10 MiB per-part and per-message total cap. Raw quarantine fetch needs no scanner. It never
implies permission to expose extracted content. Read-only files are not execution prevention.
Scanner/extraction is gated E16. Never trust filenames as paths, unpack archives, execute macros or follow
embedded links. Content extraction requires isolated no-network processing, resource bounds, scanner success
and a separate read right. Failed, encrypted or unsupported files stay quarantined. A clean scan does not make
attachment instructions trustworthy.
**D7 Storage.** Proposed `<dataRoot>/mail/<business>/` contains encrypted content blobs, SQLite
observation/outbox state, encrypted cursors, local drafts and append-only event evidence. No mail content
enters git or images. Service-only directories are 0700, files 0600. Use scoped keyed fingerprints for private
dedup fields. Content keys live at `<configDir>/secrets/mosaic-stack/mail-content/<business>/<key-id>`,
outside dataRoot and its backup set, separate from provider slots, with 0600/no-symlink checks.
Protected key expiry/retirement metadata also stays outside the mail-backup restore domain. Keep tokens, delta URLs and authentication errors out
of ordinary logs. Default durable records hold opaque IDs, state, versions and counts, not bodies, subjects,
recipient lists or attachment names. Content access is explicit and audited.
Recommended content policy for an enabled pilot: encrypted bodies and local drafts for 30 days, explicitly
fetched attachments for 7 days, minimal metadata/tombstones for 365 days. Without Jason's approved
content/retention policy, persist metadata only and show that later content fetch requires the source online.
Expired content returns an explicit unavailable result. Define backup/key expiry and restore suppression
together: restoring an old backup must not resurrect expired content or redeliver old work. Use tested
backup-copy deletion or crypto-erasure with expiry of every relevant key copy/escrow, plus restore
suppression. Encryption or one key deletion alone proves neither. Local pruning needs the Stack's
`data.delete` approval path or an explicitly approved future retention authority. Do not silently grant
automatic deletion. Write receipts, preserve legal holds and do not rewrite `<dataRoot>/runs/` or existing
audit evidence.
## Access and security
**D8 Identical access contract.** Proposed tools are `mail_list(source?,cursor?,limit)`, `mail_get(id,view)`,
`mail_thread(id,cursor?,limit?)`, `mail_attachment_get(id,part)`, `mail_ack(id,expect)`,
`mail_draft_create(id,text)` and `mail_health(source?)`. Thread views recheck every message and omit
unauthorized members/recipients. No reroute tool. All map to typed broker verbs via the existing tool
socket/MCP bridge. Local CLI and authenticated Console use the same handlers. List is paginated (default 20,
max 100). `ack` changes Stack-local state only. Drafts are local unsent records, not Graph drafts. Health
returns only authorized source metadata. No arbitrary URL, shell, source secret path, provider query language
or send tool is accepted.
Routing is human/system-only. Separate metadata, body, attachment and draft rights per business/source and
named principal. Recheck current role/grant on every read, notification and action, including caches and
derived summaries. Revoke immediately fences new uses, invalidates fetch handles and cancels owned in-flight
reads before returning content. Agents receive only mail explicitly routed to their grant scope. User mail
requires that user's or explicitly delegated access. An email specialist is not implicitly allowed to read
every user's mail. Cross-business IDs return no data.
New mediated access uses proposed `mail.access`, distinct from provider credential creation through
`credential.mint`. The chief raises it to the authenticated human Approve path, with exact
principal/target/rights/expiry bound once. Add its access grant schema and action routing only through a
Jason-approved PRD version and reviewed vocabulary/role changes. Until then new actions remain hard waits.
Grant creation stays human-only. Read/draft actions become routine only inside approved grants. Manual mode needs
per-use approval. A hand-edited grant or approving email creates no authority.
**D9 Credentials and hostile input.** Distinguish a source service principal's provider connection grant from
an agent's mediated mail-access grant. Recommend dedicated read-only ingress credentials per source/business,
held by protected Stack code and never shared with agents. This system-service ownership is a new approved
extension, not an exception silently taken to REQ-CRED-1. Configuration names only an approved absolute slot
under `<configDir>/secrets/mosaic-stack/mail/<business>/<source>/`. Proposed service-principal schema: `{kind:
"mail-service", business, source, provider, mailbox, scopes, slotPath, rotateBy, approvalId, revision}`.
Identity comes from approved configuration, not an agent argument. It grants source reads only, not human
input, grant issuance or general bus writes. Reject wildcard/unknown fields, expired approval, changed source
identity and slots inside repo/dataRoot. This extends existing schemas explicitly (S
`packages/business/src/credentials.mjs:95-102`, `packages/webui/src/sso.mjs:33`). No value or partial value is
read by a planner, returned through tools or embedded in a plan. Missing/expired/revoked slots fail closed.
Validate owned non-symlink files, rotation and revocation behavior.
Treat mail, headers, filenames, OCR and quoted replies as untrusted external data. Sender addresses, even
authenticated messages, cannot approve decisions, change policy, mint grants or impersonate the CLI/Console
human. Delimit model input and strip tool authority, but rely on broker denial, sandboxing and least privilege
for enforcement. An email asking an agent to fetch secrets, forward documents or run commands must not gain
those rights. External action proposals use existing approval paths and provenance. No automatic link fetches.
Provider endpoints are enrolled allowlists with TLS and redirect checks, not values selected from mail
content. Logs use reason codes and IDs. Mail tool results carry `untrusted: true` plus a notice modeled on S
`packages/bus/src/broker.mjs:109`. Sanitize hidden HTML, comments and control-character tricks for display
without treating sanitization as authorization. Test body, subject, display-name, filename, hidden-text and
spoofed-internal-sender injection. Mail-woken work uses a fresh isolated profile, intersected with its
existing grants: mail reads/draft, scoped fleet messages and decision.raise only, no git/credential/
external-message tools. Broker enforcement matches the tool list. Decisions and onward messages retain
`source: mail`; downstream seats cannot launder mail into authority. A powerful existing thread is never
resumed as the restricted mail turn.
**D9a Service delivery:** add a protected business-scoped delivery principal/API that accepts only approved
recipients and stable mail receipt keys, with fixed actor `mail`. S `packages/bus/src/principal.mjs:8` has no
mail principal today. Do not impersonate a user/agent or call an unauthenticated general bus write. Journal
delivery acceptance and recipient receipts atomically in the bus through this API, with grant/revoke
checks. The separate local outbox retries the same receipt key after a crash, not a distributed transaction.
**D10 Sending.** Out of scope: SMTP submission, Graph send, forwarding, auto-replies, DSNs generated by this
service and emailed digests. Block them in adapter interfaces, credentials and network policy. R21 stays
intact even if a future separate sender role is approved. Human export of an unsent draft is not authorization
for Stack delivery.
## Monitoring and system-manager integration
**D11 Health:** report desired/actual connection state, last successful scan and durable cursor commit, lag,
backlog, lease owner, retry-after, credential status, quarantine, outbox age and disk pressure. Never report
“healthy” merely because the process lives. Candidate stale threshold: three missed scan intervals. Alert once
per incident, on severity change and recovery through existing internal notifications/Console, not mail. Show
alert delivery failure locally. Alerts include IDs and reason codes, no content.
Register dependency scopes `mail-source/<business>/<source>` with Q198's manager. Network/provider outages
pause only affected intake and dependent fetch/dispatch. Independent sources and authorized stored-mail reads
can continue. Disk/audit failure halts new intake/effects. Auth/config denial stays held for correction, never
retries under another credential. Permit at most one refresh/retry of a rejected authentication attempt, then
stop and alert to prevent account lockout. Missing grants/config and explicit scope denials get no retry.
Malformed messages quarantine individually.
Apply Q205–Q207 through protected mode, not the human kill switch: mechanical source probes run independently
at 60, 120, 180 seconds, increasing by 60 to a 600-second ceiling, indefinitely, while respecting longer
Retry-After. No JEV/model availability gate and no model call for a mail health check. Mail health proves
TLS/auth and scoped source/folder access without bodies: IMAP login/NOOP alone and Graph directory `GET
/users/{id}` do not prove it. The latter needs directory permissions, not Mail.Read ([Microsoft user
API](https://learn.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0)). Use minimal scoped mailbox
operations and test denied folders/mailboxes. Retention or provider deletion can lose source mail during
outages; detect/report gaps, never promise losslessness. One lease owns retries, so adapter and manager loops
cannot multiply them. Disabled sources make no probes.
On recovery reconcile cursors/uncertain delivery receipts, pass one synthetic pipeline canary, then resume
eligible work without approval per session in manager-controlled batches at
most five with 30-second healthy intervals, and current capacity/admission checks (SM `:326-339`). After three
consecutive failed automatic exits, persist `recovery-escalated`; probes continue, work does not. Only the
authorized `system.recovery.release` path with incident/revision and diagnosis clears that latch, never the
kill switch (SM `:341-358`). Never send test mail or use a live message as a synthetic canary. Uncertain
external effects stay fenced. Human kill remains human-only to clear. These mail extensions to SM D13–D15 need
contract review and tests before operational reliance.
## Ordered build rows (all post-v1)
READY means no Jason decision blocks the stated offline deliverable. It does not mean its code prerequisites
have landed. Sage queues it now and assigns an available non-v1 seat when prerequisites/review gates pass,
without a scheduled start or calendar wait. WAIT Qn identifies the positive authority needed before that row's
governed integration. Unanswered questions never block independent READY work. All rows remain disabled by
default, use synthetic mail/fake providers and run named tests plus affected existing suites. No ready row
enables a real source or weakens current grants/hard waits. Q3/Q9/Q10 propose keeping restrictive defaults,
not gates on code that preserves them. Q7 is a technical dependency review, not a new permission gate. Q11
affects neither build nor pilot because no old repository/service is altered. Queue enrollment/landings belong
to Sage after Ludwig approves this plan, with the v1 path and protected files preserved.
| Row / gate | Deliverable and dependencies | Acceptance tests (proposed filenames, not run here) |
| --- | --- | --- |
| E01 **post-v1 READY**; Jason: none for draft | PRD addendum draft, source/data/access/service and external content-key location/lifecycle schemas in `docs/prd/`, `packages/mail/schemas/`. Core mail-source registration/policy activation awaits approved addendum/Q4; content policy awaits E13/Q6. | `packages/mail/tests/schema.test.mjs`: unknown fields, wildcards, secret values, incomplete scopes and mismatched principal/source rejected; key location excludes dataRoot/backups, mode/symlink checks, disabled schema compatible with v1. Every requirement maps to a row. |
| E02 **post-v1 WAIT Q4** | Protected slot reader, source-service enrollment/credential lifecycle and separate `mail.access` grant action in `packages/{bus,business}/`. After E01 and approved PRD. No real credential enrollment in this build row. | `packages/bus/tests/mail-grants.test.mjs`, `packages/bus/tests/mail-principal.test.mjs`: non-chief/nonhuman approval, modified targets, expiry, duplicate consume, hand grants, failed issuance undo, manual mode, immediate revoke/in-flight fencing and secret sentinel leakage. Reject repo/dataRoot slots; existing grant tests pass. |
| E03 **post-v1 READY**; Jason: none | Durable scoped ledger, encrypted state, leases, cursor/continuation, outbox and quota accounting in `packages/mail/src/{store,intake,service}.mjs`. After E01. No dependency on live grants. | `packages/mail/tests/intake.test.mjs`, `packages/mail/tests/store.test.mjs`: crash every staging/delivery boundary, concurrent owners, missing/reused Message-ID, source-scoped identity collisions, 1,000/100-MiB quotas, disk/audit failure, restart and zero-connection disabled startup. Missing/expired key fails closed; restored backup with expired key returns unavailable, never plaintext. No skips or duplicate recipient effects. |
| E04 **post-v1 READY**; Jason: none, Q7 advisory | Bounded MIME parsing/metadata quarantine in `packages/mail/src/{parse,attachments}.mjs`. After E01; independently of grants. Review license/advisories and exact-pin maintained MIME dependency. | `packages/mail/tests/parse.test.mjs`, `packages/mail/tests/parser-corpus.test.mjs`: multipart, bad charset, missing IDs, S/MIME, 30-MiB input, depth/part/text limits, traversal, hidden HTML, comments, bidi/zero-width display tricks, archive and encrypted attachment quarantine. Fuzz corpus; no expansion/execution/network, no content logs. |
| E05 **post-v1 READY** offline; core registration **WAIT Q4** | Graph descriptor/module in `packages/plugins/mail-source/m365/` against test-local registry `packages/mail/tests/fixtures/mail-source-registry.mjs`. After E01/E03/E04. `packages/plugins/src/kinds.mjs` stays unchanged until E01 addendum approval/Q4; core entry lands together with or after that approved addendum, with its first production module. Q1 selects pilot, not offline work. | `packages/mail/tests/m365.test.mjs`, `packages/mail/tests/source-contract.test.mjs`: fake Graph delta/paging, immutable moves, token/cursor expiry, 401 single refresh, 429/5xx scheduling, restart, denied mailbox, additive-scope check and alias/plus/Bcc provenance. No source writes; test-local registry works with unchanged core registry before approval. Existing plugin suite passes. |
| E06 **post-v1 READY**; Jason: none, Q7 advisory | IMAP module `packages/plugins/mail-source/imap/`. After E05's test-local shared contract; no core registration before approved E01/Q4. Review/license/exact-pin maintained IMAP dependency. | `packages/mail/tests/imap.test.mjs`: local fake TLS server, certificate denial, selected-folder scope denial, BODY.PEEK/no Seen or STORE/EXPUNGE, IDLE drop/poll fallback, renumbering, UIDVALIDITY/move reconciliation and alias/plus/Bcc/forged-header provenance. Ambiguity holds effects. |
| E07 **post-v1 READY**; Jason: none for pure evaluation | Address/rule evaluator `packages/mail/src/{addresses,rules,route}.mjs`. After E01/E03/E04; no org-file activation or delivery privilege. | `packages/mail/tests/rules.test.mjs`, `packages/mail/tests/routing.test.mjs`: port all 36 prior rule cases; bounded operators, rule version/order, previews never execute, history import window, recipient ambiguity, multiple recipients, retired agents, repeated identical mail and no content logs. |
| E08 **post-v1 WAIT Q4/Q5** | Approved org route writer and narrow service delivery API/principal in `packages/{bus,business}/`, `packages/mail/src/delivery.mjs`. After E02/E03/E07. No agent route writer/reroute. | `packages/bus/tests/mail-delivery.test.mjs`, `packages/bus/tests/mail-routes.test.mjs`: exact human approval and versioned changes, fixed actor/business, recipient receipt idempotency, spoofed principal refused, route addition grants no read, revoke before dispatch fences content, no human-input impersonation. |
| E09 **post-v1 WAIT Q4** | Mediated typed/MCP tools, per-message thread view and local drafts in `packages/{bus,harness}/`, `packages/mail/src/access.mjs`. After E02/E08. | `packages/harness/tests/mail-tools.test.mjs`, `packages/mail/tests/access.test.mjs`: same schema/results/refusals on all supported harnesses, separate metadata/body/attachment/draft rights, page caps, thread filtering, cross-business ID denial, revoke/cache races, manual mode, local ack/draft and absence of send/reroute tools. |
| E10 **post-v1 WAIT Q5** | Restricted mail-triggered launch profile, sender-auth gate and origin propagation in `packages/{mail,harness,bus}/`. After E08/E09 and Q198 admission interface. | `packages/mail/tests/mail-turn.test.mjs`, `packages/mail/tests/injection.test.mjs`: fresh context/grant intersection, no git/credential/external tools at broker and harness, body/subject/display-name/filename injection, forged auth headers, aligned DMARC/allowlist, forwarded ambiguity, no resume of powerful thread, no authority laundering through fleet messages/decisions. |
| E11 **post-v1 WAIT Q4** (mail decision schema approval) | `packages/mail/src/decisions.mjs` using the one approved Q207 decision API; no local model client. After E07/E09 and implemented Q198 decision module. | `packages/mail/tests/decisions.test.mjs`: 4-KiB closed schema, no body/free text, extra metadata denied until approved, out-of-enum/missing/uncalibrated confidence held, central fallback only, unavailable model leaves deterministic intake working; useful metadata-only outcomes or explicit unknown. |
| E12 **post-v1 READY**; Jason: none for synthetic recovery | Health/manager adapter `packages/mail/src/health.mjs`. After E03/E08 and implemented/verified SM D13-D15; fixtures can precede integration. | `packages/mail/tests/health.test.mjs`, `packages/mail/tests/outage.test.mjs`: mailbox/folder scope proof, full 60..600 cooldown and Retry-After, independent probes/one retry owner, bad-auth single retry then stop, no model probes or outage DLQ, synthetic canary, five-item/30-second batches, three-exit latch/release, human kill, alert-once/recovery and source gap reporting. |
| E13 **post-v1 WAIT Q4/Q6** | Authorized CLI/Console mail/draft/digest views and content/export/retention handlers in `packages/{mail,cli,webui}/`. After E09/E12. No live purge; keep `data.delete` hard wait. | `packages/mail/tests/retention.test.mjs`, `packages/cli/tests/mail.test.mjs`, `packages/webui/tests/mail.test.mjs`: per-user ACL on every item/export, separate decision inbox, exact deletion approval/holds/receipts, expiry of backup content and all relevant keys, restore suppression/tombstones, local draft ownership and metadata-only digest. |
| E14 **post-v1 READY**; Jason: none for offline proof | Operator runbook/acceptance packet in `docs/`; offline end-to-end harness test. After E01-E13. | `packages/mail/tests/predeploy-safety.test.mjs`, `packages/mail/tests/end-to-end.test.mjs`: port all 15 safety-gate cases; synthetic intake → route → authorized tools → local draft across supported adapters, zero source connections while disabled, from-now baseline, bounded preview, revoke/outage/restart and disable/rollback with evidence preserved. |
| E15 **post-v1 WAIT Q1/Q4/Q5/Q6**, plus Q2 if personal mail | Explicit separately authorized live pilot: enrolled agent ingress source, approved grant/route/content policy and reviewed re-pin. After E14. No v1 gate or external test mail. | `packages/mail/tests/pilot-reconcile.test.mjs` validates a redacted identity-level acceptance manifest; authorized operator supplies live receipts for delivered/held/missing identities, recipient proof, health, restart/revoke and bounded catch-up. Counts alone prove neither loss nor duplication. Complete on coverage/evidence, not a mandatory week or scheduled wait. |
| E16 **post-v1 OPTIONAL WAIT Q8**, plus Q4 access | Isolated scanner/content extractor only if Jason chooses attachment content support, `packages/mail/src/extract.mjs`. After E04/E09, separate reviewed dependency/resource boundary. | `packages/mail/tests/extract.test.mjs`: no-network/resource isolation, scan failure/unavailable/encrypted/unsupported hold, execution/archive expansion prohibited, hostile extracted text marked untrusted, access/revoke checked before release. No scanner implies no extracted-content feature. |
Run proposed Node files with `node --test <filename>`; owning reviewers select affected existing suites and
require green evidence before commit/landing. Pure store/parser/rule rows can proceed independently of
approval work. Later adapters Gmail API/JMAP/POP3 require a named need and separate rows, not a committed
expansion of this add-on.
## Questions for Jason (merged list, recommendations are not approvals)
1. **Q1 Pilot provider/domain/addresses:** which controlled domain, provider and agent ingress mailbox,
aliases or plus-tags? Recommend one approved non-USC agent mailbox, aliases after recipient proof, no
catch-all/MX change. Dedicated mailbox only for approved isolation. Its protocol pilots first; build both
adapters offline meanwhile.
2. **Q2 Personal mail scope:** which existing user mailbox, if any, may be read? Recommend none initially.
Later enrollment names user/mailbox and exact consent or delegation, separately from agent ingress.
3. **Q3 Source mutation:** mark read, flag, label, move or save provider drafts? Recommend no, read-only
sources and Stack-local drafts/acknowledgments.
4. **Q4 Service/access grants:** approve protected source-service credential ownership, distinct
per-agent/user `mail.access` approval, mail-source kind and closed mail decision schema? Recommend yes through reviewed
post-v1 PRD/vocabulary changes, chief/human approval, exact targets/rights/expiry/revoke. New access action
stays a hard wait until the approved PRD explicitly routes it. Provider credential creation remains
distinct.
5. **Q5 Unattended wake:** which senders/routes may wake granted agents without per-message approval?
Recommend allowlist plus trusted aligned DMARC, approved route, restricted fresh profile and Q198
admission. Manual mode overrides this. Ambiguous/failing mail quarantines. Neither identity nor email
approves external effects.
6. **Q6 Content/retention/deletion:** what may be retained, for how long, with what holds? Recommend encrypted
30-day bodies/drafts, 7-day explicitly fetched attachments, 365-day minimal evidence/tombstones,
metadata-only until approved. Keep exact `data.delete` authorization, backup/key expiry and restore
suppression.
7. **Q7 Dependencies:** maintained IMAP/MIME packages after license/security review and exact pins rather than
custom clients/parsers? Recommend yes, investigate Ludwig's imapflow/mailparser candidates in owning rows.
Routine technical review unless an existing repository policy adds an approval gate; no calendar wait for
package review.
8. **Q8 Attachments:** allow bytes/extraction and require scanning? Recommend metadata first, explicit capped
raw fetch into quarantine, no scanner for opaque quarantine. Extracted content waits for isolated
no-network processing, resource limits and a scanner. Do not enable arbitrary execution or automatic
archive expansion.
9. **Q9 Exposure/spend:** new listener/webhook, DNS/MX, paid infrastructure or model spend? Recommend none
initially. Outbound monitoring/internal alerts suffice. Every later exposure, address/account change or
spend needs its own authorization.
10. **Q10 Sending:** should this add-on send/forward/reply? Recommend no. R21 remains permanent for the
specialist. A future separate sender needs its own approved plan and exact-recipient human approval
through `message.external`.
11. **Q11 Prior repositories/service:** is the configured USC deployment still running, and should either
repo/service be archived or retired? Recommend no operational change here. Preserve both histories, use MP
as consolidated design reference. Any archival/retirement is a separate authorized task after actual-state
verification.
## Verification and handoff
Sources: Della 191dff32f, Ludwig 4a27b73ac, reviews 9182134e5/143415779, Mos's routing ruling.
Verify citations, coverage, row gates/tests and unchanged original blobs. No product suite,
live mail, secret-file read, install or service change here. After Ludwig approval, send Sage
this pin for refactor landing and READY-row queueing on non-v1 seats, copying Mos.
Resolve review changes without a calendar wait. Jason-gated work waits on its numbered decision.
@@ -0,0 +1,272 @@
# della-0002: phase B review of Ludwig's email plan
2026-10-21. Mos assignment mos-1220, Vikunja 460. Planning review only.
**Recommendation:** neither proposal is ready for implementation approval. Preserve
Ludwig's ingress/turn-isolation ideas and Della's grant/durability/retention controls.
Mos chooses the base in phase C. Neither original plan was edited for this review.
## Evidence pins
Every `file:line` below resolves at its named commit. Abbreviations:
| Ref | Repository/commit and document |
| --- | --- |
| L | Stack `4a27b73ac`, `docs/plans/2026-10-21_email-plan-ludwig.md`, read all 218 lines |
| D | Stack `191dff32f`, `docs/plans/2026-10-21_email-plan-della.md`, 341 lines |
| S | Stack `c3d88840c`, the implementation and PRD 1.10 base for both plans |
| MP | `mail-processor` `95b0298` |
| EP | `email-processor` `7a64921` |
| SM | Stack `2ee68cd69`, `docs/plans/2026-10-21_q198-system-manager.md` r6 |
MP contains EP plus three commits. `src/` and `tests/` compare identical. There are
36 rule test definitions and 15 predeployment safety test definitions. This confirms
L `:17,28,33`, not that any tests pass today. No prior app or suite was executed.
## 1. Wrong or unsupported claims and material gaps
**F1 (factual): Graph already fetches body content.** L `:25` says it never fetches
a body. MP and EP `src/email_processor/connectors/m365.py:27` explicitly select
`body`; `:118,129` stores HTML body content and `:135` retains the provider object.
`body_text=""` is a normalization gap, not absence of body retrieval. Correct before
using that claim to estimate migration scope or privacy exposure.
**F2 (high): role plus route is not the complete grant model.** L `:139,145,147`
makes those the access conditions, but its rows `:185-195` omit mail grant creation,
exact human approval, expiry, failed-issuance undo, revoke races and manual mode.
S `docs/prd/mosaic-stack.md:432,454,477,496,565,607` requires those controls and
keeps new actions hard waits until an approved PRD says otherwise. S
`packages/bus/src/broker.mjs:690,701` consumes approved grants once. Add explicit
source-service enrollment and per-principal mail-access grant work and tests.
Routing changes must never grant body access merely by adding a seat to a route.
Use a new approved mail-access action for mediated rights, not `credential.mint`
unless an operation actually creates a provider credential. Both use authenticated
human decision records, with new action routing governed by REQ-WAIT-3.
**F3 (high): dedup conflates content, identity and authorization.** L `:121` gives
Graph an apparently unscoped immutable-ID unique key and makes `(source_id,sha256)`
unique. L `:123` merges across sources by Message-ID plus body hash. Distinct deliveries
can have identical content or reused IDs, and two businesses must not share a mail
record/visibility decision. MP `docs/PRD.md:300,306,310,313` explicitly requires scoped
identity, keyed fingerprints and move/epoch reconciliation. Retain source-scoped
observations and recipient-specific delivery receipts. Correlation may link records
only inside an authorized scope, never collapse them or widen access by itself.
Test forged/reused Message-ID, identical repeated mail and cross-business collisions.
**F4 (high): durable mail storage does not prove durable delivery.** L `:118-127`
advances the cursor after a row commit, then routes and delivers. It does not define a
durable work state/outbox, recipient idempotency receipt, source lease or atomic
ownership claim. Crash after bus acceptance and before local acknowledgment can
duplicate work. A unique mail row alone cannot prevent that. MP `docs/PRD.md:334,343,349`
separates staging, concurrency and effects. S `packages/bus/schema.sql:70,83` has unique
bus message IDs and delivery history but no automatic mail-source idempotency binding.
Add transactional pending work, leased intake and stable per-recipient effect keys.
**F5 (high): mail health probes do not establish mailbox health.** L `:173` proposes
Graph `GET /users/{id}`, which checks a directory user, not access to the mailbox.
Its application permission is `User.Read.All`, not the proposed `Mail.Read`
([Microsoft Get user documentation](https://learn.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0)).
Use an approved minimal mailbox-scoped read with no bodies. Likewise IMAP login/NOOP
does not prove access to the selected folder. Both checks need enrolled source identity,
scope and capability validation, with no new privilege merely for probing.
L `:173` also resumes after one successful probe without stating the synthetic canary,
effect reconciliation, batch limits and observation intervals required by SM `:326-339`.
Its mention of three failed exits at L `:176` needs those exit stages to exist. Add the
persisted escalation latch/release behavior from SM `:341-358`. “Mail is not lost ...
because the server keeps it” is unsupported: retention, deletion, cursor expiry and
provider failure can remove mail. Report detected gaps, never promise losslessness.
**F6 (high): retention receipts are not deletion authority.** L `:131,186` proposes
`mail prune` with receipts/holds but does not bind it to the human `data.delete` approval
path. S `docs/prd/mosaic-stack.md:582-605` makes deletion a hard wait. Retain receipts
and holds, add exact deletion authorization, and test backup restore, content expiry,
dedup tombstone survival and non-redelivery. Choosing a retention period does not
silently route deletion within-role.
**F7 (medium): Q198 metadata allowlist is mis-cited and not automatically reusable.**
L `:66` points to SM `:420` for the 4 KiB restriction, but D12 is SM `:503-512`.
L `:126` adds sender domain, authentication results, subject length and rule summary.
Those fields are not explicitly in D12's closed allowlist of work/resource/queue/error/
capability/opaque-route fields. Require an approved, tested mail decision schema.
Metadata-only is a useful default, not proof of compliance or semantic classification
quality. Show deterministic or unknown routing when permitted metadata is insufficient.
Do not send a free-form rule summary that can contain user content.
**F8 (medium): the mechanical mail principal is new authority work.** L `:92,127`
assigns bus actor `mail`, but S `packages/bus/src/principal.mjs:8-11` verifies only
human/tester/user transport identities, and `packages/bus/src/broker.mjs:2965-2985`
checks session action authority for message sends. The design needs a narrowly scoped
trusted service delivery API/principal, not a spoofed agent session or human input.
Specify business identity, fixed actor provenance, recipient checks and allowed verbs.
This is a missing implementation contract, not evidence that service delivery is impossible.
**F9 (medium): recipient evidence needs a Graph gate too.** L `:108` correctly rejects
`To` as envelope proof, then suggests Graph recipient fields plus plus-tag without
showing they preserve the original alias/Bcc recipient. MP and EP
`src/email_processor/connectors/m365.py:115-126` only normalize visible To/Cc fields.
L M4 tests IMAP, while M5 `:189` has no recipient-provenance case. Test alias/plus/Bcc
preservation for every enrolled source and reject sender-supplied delivery headers.
When evidence is absent, quarantine rather than infer from `To` or a guessed tag.
**F10 (factual/qualification): narrow the evidence claims.** L `:19,54,209,218` treats
deployment configuration as current USC deployment evidence. It establishes intended
targeting, not that anything is currently running. No live check is authorized here.
L `:35` qualifies its secret scan, then concludes “Nothing to rotate.” A pattern scan
cannot establish that conclusion. Say no candidate found by the reported scan and
make no credential-validity/rotation claim. I have not repeated a full-history scan.
L `:68` says no mail code anywhere, but S
`tools/agent-watch/jarvis-email-watch-liveness.sh:2,18` is mail-watcher liveness code.
Say no integrated Stack mail intake/access service instead. L `:28,43` calls the rule
engine pure: MP `src/email_processor/rule_engine.py:147` logs the subject. Port rule
semantics, remove content logging, and do not describe the existing function as pure.
## 2. Ludwig strengths to graft into either base
- **G1 Restricted mail-triggered work:** L `:155` proposes mail-turn authority,
`source: mail` decision provenance and no git/credential/external-message tools.
D has least privilege but no explicit restricted mail-triggered launch profile.
Adopt a fresh isolated profile whose rights are the intersection with existing
grants. Do not resume a powerful ongoing thread or let fleet messaging launder
mail-origin requests into authority elsewhere. Broker enforcement must match tools.
- **G2 Trusted sender evidence before wake:** L `:127,158,213` adds provider-origin
Authentication-Results, allowlists and aligned authentication. D lacks an explicit
sender/authentication gate. Adopt it for unattended wake. A DMARC pass authenticates
a domain, not a human decision or permission. Define forwarded/list-mail handling as
quarantine/manual review initially. Strip forged auth headers at the trust boundary.
- **G3 Read-only thread tool:** L `:141` explicitly offers `mail.thread`. D has no
equivalent. Add a paginated view restricted per message, not an entire provider thread
that might contain recipients or messages the caller cannot see.
- **G4 Dependency investigation and parser corpus:** L `:88,186,202,215` names the
IMAP/MIME dependency work and concrete hostile fixtures. Carry that explicit package
review, exact pins and case migration into the row that first needs each dependency.
This review does not endorse an unverified package version or install anything.
- **G5 Explicit future live acceptance:** L M12 `:196` gives the eventual pilot a
visible row. D E12 leaves it a separately authorized step without a build row.
Preserve an explicit gated post-v1 pilot row, after synthetic evidence and Jason's
source/access/content approvals. Replace a fixed week/server-count comparison with
identity-level reconciliation, crash/outage/revoke cases and a defined coverage target.
Q210 favors immediate authorized progress, not a mandatory calendar wait.
## 3. Della strengths missing or materially weaker in Ludwig
- **K1 Grant lifecycle:** D `:212-235,290` separates source credentials from mediated
access, enforces human grant approval, expiry, revoke races and manual mode (F2).
- **K2 Crash-safe work:** D `:131-156,291` defines leases, cursor/outbox atomicity,
recipient receipts, bounded reconciliation and separate replay generations (F3/F4).
- **K3 Data minimization:** D `:176-199` uses metadata-only baseline, explicit fetch,
encrypted scoped storage/cursors, keyed fingerprints, backup expiry, holds and
deletion authority. L's always-raw plaintext store does not provide these controls.
- **K4 Resource bounds:** D `:138-146,158-164` names slice, retained-byte, pending-work,
MIME-depth/part/text bounds and holds under audit/disk failure. L names a raw-size cap
but leaves per-run limits unspecified and parser expansion/cumulative quotas uncovered.
- **K5 Manager contract:** D `:260-277,298` includes one retry owner, source/business
scope, synthetic canary, uncertain-effect fencing, manager batches and human-kill
persistence. Keep these and add Ludwig's explicit repeated-exit escalation.
- **K6 Security acceptance:** D E02/E08/E09/E11 `:290,296,297,299` tests hand grants,
stale approval, manual mode, revoke/in-flight races, cross-business access and backup
restore. L's cross-seat test and reduced tool list do not exercise these boundaries.
## 4. Disagreements and recommended resolution
| Ref | Difference | Side and reason |
| --- | --- | --- |
| D1 | Graph first in D E05 vs IMAP first in L M4 | Choose the first approved pilot source, not a protocol mandate. Prefer IMAP for a personally controlled IMAP mailbox. Prefer Graph if the approved mailbox is M365. Both remain post-v1 adapters. Reusing design does not require retaining USC infrastructure. |
| D2 | D aliases first vs L plus-addressing first | Take D's requirement for provider-verified original-recipient evidence. Choose aliases or plus-tags only after that proof and Jason's address ruling. Neither is an access boundary. |
| D3 | D metadata-only/explicit content vs L stores every raw message/attachment | Take D's default and scoped encryption. Raw retention is an explicit optional approved policy, not unconditional intake. Keep raw-fidelity fixtures to test parsers without retaining every live message. |
| D4 | D 30-day bodies/drafts, 7-day attachments vs L 90 days for all content | Recommend D's shorter pilot limits, 365-day minimal evidence in both. Jason decides business retention/holds. Add quotas and backup expiry rather than relying on retention days alone. |
| D5 | D scanner before isolated extraction vs L no scanner initially | Start with metadata only. Fetched bytes stay quarantined. Require isolated extraction and scanning before model-visible extracted content, or explicitly limit the first release to opaque retrieval without extraction. A read-only workspace bind does not stop code from reading/executing bytes. |
| D6 | D minimal explicitly permitted content vs L metadata-only model classification | Take L's metadata-only baseline, corrected by F7. Both need an approved mail decision schema and useful-result fixtures. A later content field needs explicit approval, not an alternate decision client. |
| D7 | D optional POP3 row vs L no POP3 build | Take L's deferral until Jason names a POP-only need. Preserve the compatibility boundary and safe UIDL/no-DELE requirements as a documented future condition, not a committed implementation row. |
| D8 | D ordinary governed agent work vs L restricted mail-turn authority | Take L's explicit restriction with G1's fresh-context and propagation conditions. Preserve a human-approved route for later promotion to ordinary work. |
| D9 | L's instance-secret slot vs D's dataRoot slot example | Take L's config-directory placement, with an explicit new service-principal schema. D `:233` is wrong against S `packages/business/src/credentials.mjs:95-102`, which forbids token files in dataRoot. Correct that example in phase C, not the validator. S `packages/bus/src/credentials.mjs:216-239` also constrains loaded locations. |
Neither interface spelling wins on safety. In phase C choose one naming scheme and
declare exact schemas once. Add descriptor/capability discovery to L's source exports.
Keep broker-mediated tools identical across harnesses, local-only drafts and no send.
Neither plan's later live row is authorization to connect now.
## 5. Merged Jason questions (no duplicate decisions)
These replace, rather than append to, both original question lists. Origin labels
refer to the original question numbers, not the new numbers below.
1. **Q1 Pilot provider/domain/addresses:** which controlled domain, provider and agent
ingress mailbox should be enrolled, and aliases or plus-tags? **Recommend** one
approved non-USC agent mailbox, no catch-all/MX change, with proven recipient
preservation. Build its protocol first. Dedicated mailboxes only for an approved
isolation need. (L Q1/Q2, D Q1.)
2. **Q2 Personal mail scope:** which existing user mailbox, if any, may be read?
**Recommend none initially**. Later enrollment names the user/mailbox and exact
consent/delegation, separately from agent ingress. (L Q8, D Q1/Q2 personal scope.)
3. **Q3 Source mutation:** should intake mark read, flag, label, move or save drafts
into the provider? **Recommend no**, read-only source access and local drafts.
(L Q3 and provider-draft portion of Q4.)
4. **Q4 Service/access grants:** approve a protected mail-service credential owner and
distinct per-agent/user access grants under the chief/human approval lifecycle?
**Recommend yes**, via reviewed post-v1 PRD/vocabulary changes, a new mail-access
hard-wait action distinct from provider credential creation, and exact targets,
rights, expiry and immediate local revoke. (D Q2.)
5. **Q5 Unattended wake:** which senders/routes may wake agents without per-message
approval? **Recommend** explicit sender allowlist plus trusted aligned authentication,
approved route/access grant, restricted mail-turn profile and manager admission.
Manual mode overrides it. Authentication failures/ambiguity quarantine for review.
None of this approves external effects or user decisions. (L Q5, D Q3.)
6. **Q6 Content/retention/deletion:** what content may be retained, for how long, and
what holds apply? **Recommend** encrypted 30-day body/draft, 7-day explicitly fetched
attachments, 365-day minimal evidence/tombstones, metadata-only until approved.
Preserve exact `data.delete` authorization, backup expiry and restore suppression.
(L Q6, D Q4.)
7. **Q7 Dependencies:** accept maintained IMAP/MIME dependencies after review and exact
version pins rather than custom protocol/parser implementations? **Recommend yes**,
assess Ludwig's named candidates during their owning rows. This is routine technical
review unless repository policy adds a dependency approval gate. (L Q7.)
8. **Q8 Attachment handling:** may attachment bytes be fetched/extracted, and is a
scanner required? **Recommend metadata-only first**, explicit quarantined fetch,
isolated no-network extraction plus scanning before exposing extracted content.
Do not enable arbitrary attachment execution or automatic archive expansion. (L Q9.)
9. **Q9 New exposure/spend:** authorize any new listener/webhook, DNS/MX change, paid
infrastructure or model spend? **Recommend none initially**. Outbound monitoring
and existing internal notifications suffice. Each later change has its own approval.
(D Q5, overlapping listener boundary in L Q2.)
10. **Q10 Sending:** should this add-on ever send/forward/reply? **Recommend no**.
R21 remains for the specialist. A future separate sender needs its own approved
plan and exact-recipient human approval through `message.external`. (L Q4, D Q6.)
11. **Q11 Prior repositories/service:** archive the older repo or change any existing
USC service? **Recommend no operational change in this assignment**. Keep both
histories. Treat MP as the consolidated design reference. Any archival or service
retirement is a separate authorized task after verifying actual deployment state,
which this review did not inspect. (L Q10.)
## Cross-review reconciliation received before publication
Ludwig's `ludwig-0002` arrived during finalization. Read all 102 lines at Stack
`143415779:docs/plans/2026-10-21_email-review-by-ludwig.md` (LR). The original plans
remain unchanged. These are phase C recommendations, not changes to their text.
- **C1 Agreed corrections:** LR `:12-19` identifies D's duplicate-check line nit,
invalid dataRoot credential slot and overbroad `credential.mint` wording. Correct
them in phase C. This review's F2 and Q4 now explicitly distinguish access rights
from provider credential issuance. LR `:62` recommends no agent reroute. Adopt that
initial tool boundary: human-approved address rules route intake, and agents cannot
widen readership by rerouting a message. Keep any later reroute/delegation design
separate, with exact access authorization and no content laundering through bus text.
- **C2 Shared graft:** LR `:26-39,63-72` agrees with most durability, grant, retention,
quota, provider-order and attachment controls above. Its secret-scan result may be
included only as Ludwig's attributed, pattern-based finding, not my independent scan
and not proof of absence or that no rotation could be needed (F10).
- **C3 Keep qualifications:** LR `:65` says key deletion is the only way expiry reaches
backups. That is too absolute: a design may delete backup copies or use crypto-erasure,
but crypto-erasure works only if all relevant key copies/escrows expire too. Specify
the actual restoration/expiry controls. LR `:67` must not imply aliases avoid delivery
provenance proof. Both aliases and plus-addressing require that proof (F9/D2).
## Review receipt
Read Ludwig's complete pinned plan, compared both proposals and checked the cited
implementation/PRD/manager passages. Confirmed repository ancestry and equal source/test
trees. Public Graph documentation was checked for F5 on 2026-10-21. No live mailbox,
secret file, service, implementation, dependency or MVP/queue change occurred. Canonical
`queue next della` returned `nothing`. Static checks cover review structure, cited
line existence, question-source coverage and unchanged original plan blob. No product
suite or live validation was run. Commit/push this review on `della/email-plan`, then
deliver della-0002 immediately to Mos and Ludwig under Q210. Phase C remains Mos's call.
@@ -0,0 +1,35 @@
# Brief: library sync follow-up, client timeout, STATUS codes and README (C2-03a deferrals)
Sage, 2026-10-21, for the deferrals Dewey named in the row 222 approval (#1723 comment 30138). Row 222
landed as 0b33f5e9d. Owner Megavolt, reviewer Dewey, gate owner Sage. Chained after row 196: `human-cli.mjs`
is pinned by hash in the channel proof, and row 196 changes that proof, so this row builds on 196's landing.
## What this row does
1. **Client timeout.** `library.sync` can run longer than the human client's 5 s timeout. Today that fails
safe: the sync keeps running, a second call gets `library-sync-busy`, and `library.status` shows the
result. Give `library.sync` a longer client timeout in `packages/bus/src/human-cli.mjs`, and in the
Console route when one exists. Re-pin the channel proof for the new `human-cli.mjs` hash, and say in
BUILD.md which proof file and test change.
2. **STATUS codes (Dewey's 215 N3).** Add entries in `packages/webui/src/bus.mjs` for `library-sync-busy`,
`library-diverged`, `library-accept-stale`, `library-accept-invalid`, `library-credential-missing` and
`library-fetch-failed`, with a test that each code maps to its status.
3. **README (222 N4).** In `packages/library/README.md`'s Sync section, say that a client timeout leaves the
sync running and that `library.status` shows its result. Correct BUILD.md's "the Console gets the call"
line in this row's BUILD.md: `cli.mjs` passes `librarySync` to `startServer`, and `serve.mjs` doesn't
read it yet.
Dewey's other 222 notes don't bind this row: N1 (a close waits on every entry left in a pass), N2 (a held
tip still gets its tree read) and N3 (held states log every tick). Fix one here only if it is a few lines in
a file this row already changes, and say so in BUILD.md. Otherwise list it as deferred.
## Boundaries
- No test touches `~/.mosaic-dev`, `~/.config/mosaic-dev`, the live units, a slot file, .53 or .54.
- No Gitea fetch against a real server. Use the fakes row 222's tests use.
## Gate
Dewey approves the manifest. On Sage's slot: the bus, library and webui suites whole, and every
`scripts/test-*.sh`, green. A mutant that drops the longer timeout and one that drops a STATUS entry are
both killed.
@@ -0,0 +1,54 @@
# Brief: case2-unit.sh and case1-accept.sh move to --tester-socket (F27)
Sage, 2026-10-21, for Mos's ruling mos-1198 item 4. Owner Gander, reviewer Duckworth, gate owner Sage.
Chained directly after row 196: it starts on row 196's landing and rebases onto it. Until this row lands, no
Case 1 or Case 2 host (.53, .54) takes a stack copy that carries row 196.
## Why
Row 196 retires the Console's `--tester-port`. `packages/webui/src/cli.mjs:30` refuses it with
"--tester-port retired; activate explicitly with --tester-socket". Two root scripts still produce or require
the old flag, so a unit they render won't start on a copy that carries 196:
- `scripts/case2-unit.sh` takes `--tester-port N` for the factory profile (:163, :193-194, :204) and renders
`cli.mjs serve $FLAGS` into the Console unit's ExecStart (:356).
- `scripts/case1-accept.sh` reads `--tester-port` from the saved installer args (:65), refuses args that
lack it (:88), and checks the tester port in P10 tester-port-squat (:430, :432).
Gander's static audit on row 196 r5 lists every caller and test: `agents/gander/work/queue-196/r5/
TESTER-PORT-AUDIT.md` and `tester-port-occurrences.json` at 91e3696ef.
## What this row does
1. `case2-unit.sh`: the factory profile takes `--tester-socket` and renders it into the Console ExecStart.
`--tester-port` is refused with stack-release.sh's migration wording (`scripts/stack-release.sh:808`,
"retires tester TCP; use --tester-socket"), naming this script in place of the target copy. The
library profile still refuses any tester flag.
2. The socket path must work under the unit's hardening (ProtectHome, PrivateTmp, ReadWritePaths, :340-343).
Use the path row 196 derives (`<dataRoot>/console/tester.sock`) and the parent checks stack-release.sh
makes (:815). Say in DESIGN.md how the rendered unit reaches it.
3. `case1-accept.sh`: accept the socket form in the saved args, and refuse args that carry `--tester-port`
with the same wording. P10 checks what the socket form needs in place of a port squat check. Say what
it checks and what each outcome line reads.
4. Tests: `packages/cli/tests/case2-unit.test.mjs` and `case1-accept.test.mjs` move their fixtures to the
socket form, and each gains a refusal test for `--tester-port`. Fakes only, as they use now.
5. Leave the deliberate legacy paths alone: `scripts/webui-service.sh`, `scripts/stack-release.sh`,
`scripts/instance.sh`, their tests, `stack-release-fixture.mjs`, `case1-hostcheck.test.mjs` and the
negative calls in `tester-cli.test.mjs`. If one of them needs a change, stop and ask.
## Static check
The check covers scripts as well as tests (mos-1198 item 4). After this row, `git grep -n -- --tester-port
scripts packages` returns only the legacy paths in step 5 and the refusals this row adds. BUILD.md lists
each remaining hit and why it stays.
## Boundaries
- No test touches `~/.mosaic-dev`, `~/.config/mosaic-dev`, the live units, a slot file, .53 or .54.
- This row doesn't run case1-accept.sh or case2-unit.sh on a host. That is a window with Jason.
## Gate
Duckworth approves DESIGN.md, then the manifest. On Sage's slot: the cli suite whole, the webui suite whole,
and every `scripts/test-*.sh`, green. The mutants named in DESIGN.md are killed, at least one per refusal.
The static check above holds on the candidate.

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