Compare commits

..
Author SHA1 Message Date
jason.woltje 42b47f36c4 gander: guard unique mutation targets and separate SSO ordering mutant 2026-10-21 14:09:17 -05:00
jason.woltje 1bb1e8dd60 gander: pin SSO and tester socket release argument order 2026-10-21 14:08:31 -05:00
jason.woltje ed99ca2259 gander: record r2 stop two timings and approved row 252 notes 2026-10-21 14:02:18 -05:00
jason.woltje ab1f0e576f gander: classify runtime mutation failures and bound process groups 2026-10-21 13:56:12 -05:00
jason.woltje d218e14bd9 gander: record row 196 rebase gate stop and row 252 design 2026-10-21 13:55:41 -05:00
jason.woltje 64539c96bb gander: preserve row 196 human boundary build for rebase 2026-10-21 13:48:20 -05:00
Herbandjason.woltje a365237133 test: resolve the real Docker binary inside lane fixtures
(cherry picked from commit c45ddeb6bd)
2026-10-21 13:28:40 -05:00
jason.woltje 5afff54908 brief: T27 row 251 records the monotonic-boundary check, 8 of 80 adds lost the key (mos-1185 item 2) 2026-10-21 12:20:21 -05:00
jason.woltje f33932921c brief: T27 lists no identities right after ssh-add -t 1, Vikunja #453 (mos-1185 item 2); row 197 lane-b observation (item 3) 2026-10-21 12:12:46 -05:00
jason.woltje 1be656ac97 brief: K12 reads cgroup.events after cgroup.kill under load, Vikunja #450 (mos-1182 item 3) 2026-10-21 11:09:37 -05:00
jason.woltje 19d3d5f026 sessions: Filbert row 66 round 4 review, approve 2026-10-21 10:21:00 -05:00
jason.woltje 6c75887b0c sessions: Sage 10-21 afternoon (rows 249, 197, 222, 66, queue revs 2642-2904), with pending Dewey and Researcher lines 2026-10-21 10:19:14 -05:00
jason.woltje 0636721e60 queue: revs 2642-2904 (rows 234, 235, 249 landed; 212, 213, 218, 221, 231, 247 landed; 228, 233, 238, 240, 195, 245, 248 landed; 197 r8 approve, 215 r1 approve, 66 round 4; Case 2 and installer row notes)
Sweeps other seats' revs: Gizmoduck (234, 235, 249), Duckworth (234, 235, 195, 245, 249 verdicts),
Darkwing (233, 240, 195, 245), Filbert (233, 240, 215 verdicts), Gosalyn (212, 221, 231, 247),
Morgana (213), Researcher (228), Launchpad (238), Herb (197, 215), Webby (197, 212, 213, 218, 221,
228, 231, 238, 247, 248 verdicts). The rest are Sage's notes, landings, moves and review requests.
2026-10-21 10:18:29 -05:00
jason.woltje 7b9737953a deferred: row 248 Webby N1, the grant executor test sleep 30 can outlive its test; Duckworth deferred it in the row 249 review (#1751 c30070) 2026-10-21 09:55:13 -05:00
jason.woltje 9de5a57abd row 249: row 235 follow-up, a grant whose install never returned records hostLineLeft, since the remote add can outlive an abort or a timeout (#1751)
An abort or a timeout kills only the local ssh. A non-pty sshd command gets no SIGHUP, so the remote
add can still write its line after the undo's remove. The broker now marks install done only after
`await install(...)` returns. When it never returned, the undo records hostLineLeft with the tag, so
grant.failed and status name the line, hosts.remove refuses until `mosaic hosts sweep` clears it.
The :432 test comment now matches. Row a's enroll does no remote add, so it has no such race.

Candidate gizmoduck/row249-on-8a04 05595794b (Gizmoduck), manifest 3a969bb136bc, approved by
Duckworth in round 1 (rev 2889-2890 request c30068, verdict c30070). Gate on Sage's slot 14:53Z
(sage-2026): grant-executor.test.mjs whole 39/0 and bus 598/0 with no stub left, and C1, which
drops the `if (!installed)` branch, fails G8 and both G23 tests. Webby's N1 on X5 `sleep 30` is
deferred by Duckworth (DEFERRED.md).
2026-10-21 09:54:29 -05:00
jason.woltje 8ca064158a sessions: Filbert row 215 candidate review r1, approve 2026-10-21 08:43:38 -05:00
jason.woltje 8a04f4aa4a row 248: row 235 follow-up, the grant-executor hold-add and hold-remove stubs stop when their test dir is gone, so no stub outlives its test (#1750)
The row 197 r8 D5 proof stopped on a leftover hold-add stub. G23 and G13 write release, finish and
remove the test dir before the stub polls again, so the stub looped forever on sshd's side. 4 runs
of the file left 7 stubs. Both hold loops now also stop once $D is gone.

Candidate 4d99c178b, approved by Webby in round 1 (rev 2883, webby-0080). Gate on Sage's slot
13:05-13:07Z: the whole file 3 runs at 37/0 with no stub left, bus 596/0, and a mutant that drops
the hold-add check leaves 2 stubs. The late remote add the leak exposed is row 249 (#1751).
2026-10-21 08:16:49 -05:00
jason.woltje bc579f7a84 docs: host run sheet section 4, the apt-get download W: line as root is expected, and the profile's sha256 is checked again at /etc/apparmor.d (mos-1168) 2026-10-21 08:14:11 -05:00
jason.woltje 6de1ebc629 plans: row 235 follow-up brief, the grant undo records a host line it can't prove gone (mos-1166, gizmoduck-0139, duckworth-0091) 2026-10-21 08:11:21 -05:00
jason.woltje 540239b6b7 plans: row 235 follow-up brief, the held grant-executor stubs stop when their test dir is gone 2026-10-21 08:03:55 -05:00
jason.woltje 6d5545d442 row 245: row 195 follow-up, F9 runs node as root only at the pinned root-only path, the header and TOOLS.md list what root runs, F6 refuses .. after claude-<pin>/ (Duckworth N1-N3, #1693)
Darkwing's ef8b4439e (darkwing/row245), Duckworth round 1 APPROVE (rev 2849, comment 30043),
manifest 69bc05f4f706, 5 paths. Slot run sage-1961 (darkwing-0107): singles 23/0, mutants 16/16
killed, N1 and N3 among them. DESIGN.md:336-338 stays unedited, superseded by gosalyn-0087.
Sage gate on 856c1826b plus these 5 paths (12:24-12:35Z): case1-accept.test.mjs alone 23/0,
cli 681/0, every scripts/test-*.sh 429/0.
2026-10-21 07:35:39 -05:00
jason.woltje 856c1826bc row 247: C2-20b persona import follow-up, keys from config.yaml print as JSON strings, the dry run applies C2-08's fence and refuses, privateDir refuses an EEXIST race by name (#1749)
Gosalyn's 6d43618a3 (gosalyn/c2-20b), Webby round 1 APPROVE webby-0076 (rev 2852), manifest 6d3f279f692b,
4 paths. Webby's row 221 notes N1, N2 and N4. Slot runs sage-1969 (red base, the builder's comma
regression) and sage-1971 (14/0, M1-M4 killed). M1/M2 sit at :248 in this commit, not :237 as
BUILD.md says (gosalyn-0111). JSON.stringify leaves DEL, C1, U+2028/9 and bidi raw, a known limit
by sage-1964.
Sage gate on 5c6f73ed7 plus these 4 paths (12:10-12:21Z): the test file alone 14/0, cli 680/0,
every scripts/test-*.sh 429/0. f8276c5eb adds only docs/SESSIONS and docs/case2 sheets no test reads.
2026-10-21 07:22:07 -05:00
jason.woltje f8276c5eb3 row 218: C2-12 setup sheet for Melanie and the library survey run sheet (#1719)
Doc only, Sage's sage/row218 89ba79d94 (three commits squashed). Webby APPROVE round 1 (webby-0075,
rev 2848), round 2 (webby-0077, rev 2856) and round 3 (webby-0078, rev 2860). docs/case2/SETUP-SHEET.md
tells Melanie what the agents read, the playground, link and property forms, and what changes for
Bean and Chet, with M5's 1,200 character reply total (mos-1165). It goes to her only after C2-06
lands and on Jason's go (#419). docs/case2/SURVEY-RUN.md is design 10 step 7's run sheet: the
bare mirror as the source, a node check, output lines and refusals from survey.mjs. Claim sources
and rulings are in agents/sage/work/queue-218/BUILD.md. No test reads either doc path.
2026-10-21 07:05:39 -05:00
jason.woltje b65f3a0e84 sessions: Darkwing row 245 slot green, round 1 in review 2026-10-21 06:50:41 -05:00
jason.woltje 5c6f73ed75 plans: C2-03a brief, the mirror HEAD is a symbolic ref to refs/heads/served so the C2-12 survey reads the served commit
The survey reads HEAD and has no ref flag, and design 10 step 7 surveys the bus mirror. Row 222 is not
built yet, so this adds one requirement and one test to its brief before the build starts.
2026-10-21 06:39:33 -05:00
jason.woltje 947101d8a3 sessions: Sage 235, 221 and C2-20b landings, 197 r7 request and proof prep 2026-10-21 06:28:49 -05:00
jason.woltje 335bb3e30f plans: C2-20b brief (row 221 follow-up, Webby N1, N2 and N4) and DEFERRED for N3, Morgana's 213 B3 anchor
Webby approved row 221 round 1 (webby-0073) with four notes that don't block. N1, N2 and N4 are in
scripts/case2-persona-import.mjs and go in before Jason's one import run (sage-1941), as row C2-20b,
owned by Gosalyn and reviewed by Webby. N3 is tooling outside the product and waits in DEFERRED.
2026-10-21 06:23:41 -05:00
jason.woltje 3007664504 row 221: C2-20 Bean and Chet persona import, stage and install script with a count-only dry run, personaTextFault exported from bundle.mjs, yaml 2.9.0 exact, fixture Hermes home and read trace (#1722)
Lands Gosalyn's round 1 candidate, 8f403d0d4 on gosalyn/c2-20-r2 (48f044ccc plus its BUILD.md, on
62ddc96cc), approved by Webby (webby-0073, REVIEW-r1 a0a00630e, rev 2808). 22 paths, manifest
20f05e3a6ad5. No 221 path changed between 62ddc96cc and this commit's parent. Patch-id dfbb754c6fb4, the
same as 62ddc96cc..8f403d0d4. The two state.db fixtures and auth.json are short text stand-ins.

Webby's N1, N2 and N4 go to the C2-20b follow-up row before Jason's import run. N3 (mutants.py:48
anchor) is in DEFERRED.md. The script itself runs only under Jason's go (design 10 step 6, 9.1).

Evidence: Gosalyn's singles on my slot, 419/0 on 48f044ccc (gosalyn-0104).
Gate on my slot, 11:07:50-11:19Z on b1fece6f0 plus the 22 paths: case2-persona-import.test.mjs alone
11/0, harness 140/0 (bundle.mjs changed), cli 677/0, and every scripts/test-*.sh green (auth 15,
conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86, queue 156 and 28,
release 14, task 98, all 0 failed). Lands before row 197 r7: no shared paths, and r7's proof candidates
both carry it (sage-1954).
2026-10-21 06:19:41 -05:00
jason.woltje b1fece6f0a row 235: machine grant row b, raise binding, executor, reconcile, revoke and sweep, creative roles refused at raise, recheck and host.run, N10 (#1741)
Lands Gizmoduck's round 1 candidate, ed4193316 on gizmoduck/row235-e (one commit on 62ddc96cc), approved
by Duckworth (duckworth-0089, REVIEW-r1 d9ecec514 on duckworth/reviews, #1741 comment 30022). 13 paths,
manifest dad377ca8fe4. No 235 path changed between 62ddc96cc and this commit's parent. Patch-id
c355d2fc457d, the same as 62ddc96cc..ed4193316. broker.mjs keeps row 213's import and decision.raise
creative refusal as landed.

Creative roles (mos-1163, mos-1164, sage-1908): #hostDefinition refuses host-creative-role at the raise
and at the step 1 recheck, and #hostAccess refuses it at host.run before reading the grant record, which
stays for hosts.revoke. Duckworth checked from the code that a creative role can't raise, approve or
spend a host grant, and ruled the upsert mutant equivalent.

Evidence: Gizmoduck's runs on my slot. B1-B37 on 5df4b52c5, and run 2 on 534f2254a (10:12:52-10:14:51Z):
grant-host 9/9, grant-executor 37/37 and grant-enroll 24/24, each run whole, with B16, B23, B27, B38 and
B39 killed. B1-B39: 38 killed, and B17 is equivalent (sage-1904).
Gate on my slot, 10:30:55-10:42:43Z on 29ce1e360 plus the 13 paths: bus 596/0 (its first whole run on
this head, duckworth-0089 N1), business 200/0, cli 666/0, and every scripts/test-*.sh green (auth 15,
conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86, queue 156 and 28,
release 14, task 98, all 0 failed).
2026-10-21 05:42:52 -05:00
jason.woltje aa19a2464e sessions: Darkwing row 245 built, slot asked 2026-10-21 05:12:45 -05:00
jason.woltje 29ce1e3602 row 231: C2-01b library reader and survey fixes: control and bidi escapes, front matter list links, an unresolved body embed to a non-.md target is an attachment (#1733)
Lands Gosalyn's round 2 candidate, f37a65857 on gosalyn/c2-01b-r2 (base fefd54e13; commits 3647a5a93,
88daaac50, f37a65857), approved by Webby at merge scope (webby-0072, REVIEW-r2 efd6c1f02 on webby/reviews,
#1733 comment 30005, rev 2787). Nine paths, manifest 312c16fc4f65. No 231 path changed between fefd54e13
and this commit's parent. Patch-id 59b98b1b3e00, the same as fefd54e13..f37a65857.

Evidence: Gosalyn's singles on my slot, 61/0 on r2 (gosalyn-0099; run 1 60/1 was a test order defect).
Gate on my slot, 10:05:31-10:07:52Z on 62ddc96cc plus the nine paths: library 45/0, and every scripts/test-*.sh
green (auth 15, conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86,
queue 156 and 28, release 14, task 98, all 0 failed).
Webby's N4 (README:88 "A link to ... an attachment is R4" next to the embed paragraph) is doc-only and
stays as restored (sage-1917).
2026-10-21 05:09:23 -05:00
jason.woltje 62ddc96cc7 plans: row 195 follow-up (Duckworth N1-N3) and the .53 window row, which carries 195's done condition after its early done move 2026-10-21 05:03:21 -05:00
jason.woltje d6cff0cdd8 deferred: Duckworth's N1-N3 on row 195, the Case 1 acceptance script's root parse order, header and prefix match 2026-10-21 05:01:46 -05:00
jason.woltje 361ba2387f row 213: Case 2 C2-08 creative roles, persona file and block, M1, M2, M6, M10, prompt passed by file (#1714)
Lands Morgana's round 4 candidate, 2b3e67e36 (base c771a36f4, branch morgana/row213-r4), approved by Webby
(webby-0071, REVIEW-r4 02bb0afb3, rev 2775). Manifest digest 26d4d0ecff56, 35 paths, plus Morgana's
four packet files under agents/morgana/work/queue-213/ (base.txt, BUILD.md, candidate-manifest.sha256,
mutants.py): 39 paths, blobs and modes from the candidate. No 213 path changed between c771a36f4 and
this commit's parent. Patch-id f313e69b6f7e, the same as c771a36f4..2b3e67e36.

Evidence: Morgana's slot run on 2b3e67e36 (sage-1844): singles 191/0 and 31/31 mutants killed.
Gate on my slot (sage-r195land, base 38b83ea5c plus 195's 5 and 213's 39 paths, 09:45:52-09:57:52Z): business 200/0, bus 542/0, harness 140/0, cli 663/0; scripts auth 15/0, conductor 17/0, config 31/0, discord 78/0, extension-package 18/0, foundation 44/0, land-work 86/0, queue 156/0 plus 28/0, release 14/0, task 98/0; the models mutant L1 killed (launch-settings 15/1), restore matched.
Between the gate base 38b83ea5c and 4364dddb0 only docs/plans/2026-10-20_host-credentials.md changed (01e1fe512, 4364dddb0), which no test reads. Row 195 landed as this commit's parent; it shares no path with 213.
Webby's N2 (a host grant for a creative role) is a row 235 acceptance item (mos-1163), not a deferral.
2026-10-21 04:58:30 -05:00
jason.woltje b650146479 row 195: scripts/case1-accept.sh, root's Case 1 acceptance (F1-F11, P1-P11), its test, TOOLS.md entry (#1693)
Lands Darkwing's manifest round 1 candidate, 37171f65b (base 4ced5e63d), approved by Duckworth
(duckworth-0088, REVIEW-m1 6afcee2a5, #1693 comment 29988, rev 2758). Five paths:
scripts/case1-accept.sh (100755), packages/cli/tests/case1-accept.test.mjs, docs/TOOLS.md,
agents/darkwing/work/queue-195/DESIGN.md (r7, approved duckworth-0087) and BUILD.md. No 195 path changed
between 4ced5e63d and this commit's parent. Patch-id 656e43ff25de, the same as 4ced5e63d..37171f65b.

Evidence: Darkwing's slot runs: single 22/22 (sage-1829), 12/12 mutants killed and gate cli 662/0 plus
selftests 429/0 on ddf7dce6c (sage-1865, sage-1870).
Gate on my slot (sage-r195land, base 38b83ea5c plus 195's 5 and 213's 39 paths, 09:45:52-09:57:52Z): business 200/0, bus 542/0, harness 140/0, cli 663/0; scripts auth 15/0, conductor 17/0, config 31/0, discord 78/0, extension-package 18/0, foundation 44/0, land-work 86/0, queue 156/0 plus 28/0, release 14/0, task 98/0; the models mutant L1 killed (launch-settings 15/1), restore matched.
Between the gate base 38b83ea5c and 4364dddb0 only docs/plans/2026-10-20_host-credentials.md changed (01e1fe512, 4364dddb0), which no test reads.
Duckworth's notes N1-N3 go to docs/plans/DEFERRED.md in the next commit.
2026-10-21 04:58:17 -05:00
jason.woltje 4364dddb0d plans: row b (235) host.run also refuses a creative agent at spend time, grant record kept for revoke (mos-1164, sage-1908) 2026-10-21 04:49:11 -05:00
jason.woltje 01e1fe5128 plans: row b (235) refuses a host grant to a creative role, after row 213 lands (mos-1163, Webby N2 on 213 r4) 2026-10-21 04:40:22 -05:00
jason.woltje 38b83ea5c6 sessions: Sage, 212 landing, 197 proof stop and r6, rows 243/244, row 66 r4b 2026-10-21 04:20:46 -05:00
jason.woltje fefd54e137 deferred: N7 lane image base inspect; brief for two gate environment rows (mos-1162)
N7: lane-image.mjs:41 inspects a base BuildKit never tagged (row 197 proof try 1).
Brief: the process.test.mjs mode chmod row and the fixture PI_AUTH_FILE row, both after row 197.
2026-10-21 04:15:06 -05:00
jason.woltje ee7b2f1376 row 212: packages/library, mapping, scope, reader, resolver, R-rules, renderItems, survey (#1713)
Lands Gosalyn's round 3 candidate, a2c72ac95 (commit candidate, base c771a36f4), approved by Webby
(29979, rev 2731). 21 paths: the new packages/library (src, tests, fixtures, package.json, README),
scripts/stack-copy.sh (packages/library/src in COPY_PATHS), packages/cli/tests/stack-copy.test.mjs,
scripts/mosaic and agents/gosalyn/work/queue-212/BUILD.md. The 21-path patch-id, ea84d3e35470, equals
the candidate's.

Evidence: Gosalyn's slot run on round 3: 20 files 451 pass, 0 fail; test-queue 28/0 (gosalyn-0086).
Landing gate (Sage, 08:51-09:03Z, base 4ced5e63d) on a tree that also held row 197's 32 paths:
GATE_PASSED, node tests 981 pass 0 fail (library 39, cli 681, control-board 38, harness 16, webui 51, plus
the scripts/test-*.sh node suites), selftests 429 passed 0 failed. Row 212 lands alone (mos-1162); row
197 waits for an r6 after its lane proof stopped. No 212 path changed between c771a36f4 and this commit's
parent.
`queue review verify-commit 212` -> ok row 212 round 3: a2c72ac95 matches the commit candidate (21 paths).
2026-10-21 04:12:08 -05:00
jason.woltje f6c70f9441 sessions: Darkwing, row 195 build in review (37171f65b, rev 2756), slot green 22/22, 12/12 mutants, gate 662/0 + 429/0 2026-10-21 03:52:15 -05:00
jason.woltje b3b2374eaf sessions: Filbert, row 215 manifest r2 approve 2026-10-21 03:37:47 -05:00
jason.woltje 696255f883 sessions: Filbert, row 215 manifest review, changes requested (B1-B3) 2026-10-21 03:29:39 -05:00
jason.woltje 059577886c deferred: gate runs mount the host pi auth file (Webby N4 on the row 197 proof driver, traced by Herb) 2026-10-21 03:28:33 -05:00
jason.woltje 4ced5e63d4 sessions: Sage, rows 240, 228 and 238 landed (c515273db, 86249ac55, d49b1f2bd), 197 r5 rebuild, Gosalyn 212 slot (sage-1788..1826) 2026-10-21 02:29:11 -05:00
jason.woltje e7078e6a85 deferred: Webby's row 238 N1, the sso-config first-step hint has no business (#1744) 2026-10-21 02:27:43 -05:00
jason.woltje d49b1f2bd3 row 238: stack-release's sso-config first step carries --instance NAME on a named instance (#1744)
Lands Launchpad's round 3 candidate, manifest 1bf831f3 (4c90099ea on launchpad/row238-r4, base
c515273db), approved by Webby (29956, rev 2717). Three paths: scripts/stack-release.sh, packages/cli/tests/
stack-release.test.mjs and docs/TOOLS.md. It is row 200's follow-up N1. Round 3 only merges docs/TOOLS.md
onto the row 240 landing: the landed blob plus the one hunk at :599. The 3-path patch-id, b9d0bb0ee3c7,
is the same in all three rounds, and stack-release.sh stays 100755.

Evidence: Launchpad's slot run on round 2, 2afbfbb01: stack-release.test.mjs 196/0 (sage-1794). Sage's
combined gate with row 228 on c515273db plus both rows' paths (07:11:30Z to 07:23:34Z): packages/seat 165 pass, 0 fail; packages/cli 640 pass, 0 fail; all ten scripts/test-*.sh 585 pass, 0 fail (out/, run.log). Row 238's mutant, the sso-config first step dropping --instance NAME, was killed (2 fails), survived: none, and stack-release.sh was restored to its blob.
`queue review verify-commit 238` matches the manifest on all 3 paths. Round 1's N1 stays on the #1744
carry list.
2026-10-21 02:24:25 -05:00
jason.woltje 86249ac552 row 228: ZAI_API_KEY in the session env allowlist (#1729)
Lands Researcher's round 3 candidate, manifest 82cf98c6 (cf18c42b9 on researcher/row228-r3, base
c771a36f4), approved by Webby (29949, rev 2708). Three paths: packages/seat/src/session.mjs (ZAI_API_KEY
in ENV_ALLOW), packages/seat/tests/session.test.mjs (the pass-through test) and packages/cli/README.md
(the line, with the round 1 and round 2 N1 trims at :711 and :718). Round 3 is the round 2 rebase onto
the row 234 landing; the two seat files carry the blobs approved in rounds 1 and 2.

Evidence: Researcher's slot run on cf18c42b9: session.test.mjs 13/0, host.test.mjs 42/0 (sage-1796).
Round 2's seat and cli runs, 94/0, and its killed mutant stand, since the seat blobs are unchanged.
Sage's combined gate with row 238 on c515273db plus both rows' paths (07:11:30Z to 07:23:34Z):
packages/seat 165 pass, 0 fail; packages/cli 640 pass, 0 fail; all ten scripts/test-*.sh 585 pass, 0 fail (out/, run.log). bus runtime.mjs and webui reads.mjs import only readSessions from session.mjs, which this
change doesn't touch. `queue review verify-commit 228` matches the manifest on all 3 paths.
2026-10-21 02:24:25 -05:00
jason.woltje 59f817c321 sessions: Darkwing, row 195 DESIGN r7 pushed (5425ea2f3), Duckworth review requested (Gitea 29960), row 226 items to Gosalyn 2026-10-21 02:15:49 -05:00
jason.woltje c515273db7 row 240: installer I4, root's host check, its pins and the boot unit; I3's N1, N4 and N5 (#1746, Vikunja #432)
Lands Darkwing's round 1 candidate, manifest 573ece95a4a9 (ad1b23ab2 on darkwing/row240-r3, base
c771a36f4), approved by Filbert (29945, rev 2696). Nine paths: scripts/case1-hostcheck.sh (new),
scripts/case1-pins.sh (new), packages/cli/systemd/mosaic-hostcheck.service (new),
packages/cli/tests/case1-hostcheck.test.mjs (new), scripts/stack-upgrade.sh,
packages/cli/tests/stack-upgrade.test.mjs, docs/guides/host-run-sheet.md, docs/TOOLS.md and
agents/darkwing/work/queue-240/BUILD.md. File modes match the candidate. case1-hostcheck.sh and
stack-upgrade.sh are 100755. case1-pins.sh is 100644, because the host check sources it.

Evidence: Darkwing's first slot run failed one test, the N1 test's after hook (ENOENT once the
fixture had removed its root). The fix b45852daa is test-only. The second run on b45852daa was green:
singles 37/0, mutants M1-M8 8 of 8 killed, cli 639/0, scripts 585/0. Since c771a36f4 no commit
touches the nine paths. Sage's gate on 060a17b54 plus the nine paths (06:45:05Z to 06:56:21Z):
packages/cli 639 pass, 0 fail, and all ten scripts/test-*.sh 585 pass, 0 fail (out/, run.log). `queue review verify-commit 240` matches the manifest on all 9 paths.

Filbert's notes: N1 (the boot unit orders only mosaic-bus@mosaic-stack) and N2 (the run sheet writes
the apt hook before the first --check) are in docs/plans/DEFERRED.md. N3, repeated DRIFT lines after
bwrap is closed, is by design and is noted with N2.
2026-10-21 01:56:47 -05:00
jason.woltje da3051ccc6 deferred: Filbert's row 240 N1 and N2 (29945, #1746); 10-24 window draft Q1 takes the mos-1161 ruling 2026-10-21 01:50:34 -05:00
jason.woltje 060a17b549 sessions: Filbert, row 240 round 1 approve (29945, rev 2696) 2026-10-21 01:41:13 -05:00
jason.woltje ffd90e7fc4 sessions: Darkwing, row 240 in review round 1 (ad1b23ab2, manifest 573ece95, revs 2694 and 2695), with its BUILD-LOG entry 2026-10-21 01:37:57 -05:00
jason.woltje 02709125a6 sessions: Sage, row 234 landing c771a36f4, 235 briefed, rebuilt-row slot order, 10-24 draft NEW (sage-1766..1787)
Doc only. The 10-24 window draft 0 now names NEW c771a36f4 and adds row 233 and the T46 untimed tables.
2026-10-21 01:20:30 -05:00
jason.woltje c771a36f4b row 234: machine grant row a, host enrollment, store records and bus schema 3i to 3j (#1740, Vikunja #436)
Lands Gizmoduck's round 3 candidate, manifest 4782c8b5ecb7 (c783d5e39 on gizmoduck/row234-r3, one commit
on 57f59e0c3), approved by Duckworth: content on round 1 (861182fa6, 29919), the round 2 rebase check
(29928, rev 2670) and round 3 (REVIEW-r3.md 7c8c38b69, posted 29935, rev 2683). 41 paths.

Round 3 fixes the two failures in Sage's combined gate with row 233: bus schema.test.mjs now counts 18
tables and 71 triggers for the new hosts and host_grants tables, and stack-release.test.mjs T46 puts both
tables in the untimed restore-guard list, since every write shares a transaction with a grant.* event.
Round 3 rebuilt on the row 233 landing as one commit. 38 of the 39 round 2 blobs are unchanged and BUILD.md
is append-only. Gizmoduck's singles on round 3: stack-release 196/0, schema 1/0.

Evidence: Gizmoduck's slot runs, 25 of 25 mutants killed (A22b and A23 included, A9 structural), singles on
r2 387/0 over 47 files. Sage's landing gate on the c783d5e39 tree: 06:02:12Z to 06:15:26Z, every package except queue 2890 pass, 0 fail (15 packages; bus 539, cli 622), and all ten scripts/test-*.sh 585 pass, 0 fail.

This is code only. The live stack runs from a pinned copy at schema 3i. The 3i to 3j migration runs in the
10-24 window (mos-1145), agent-run under Q173, with a backup first. Duckworth's N10 (hosts.mjs
Object.hasOwn) and N12 (doc-only) go to row 235's carry list.
2026-10-21 01:15:43 -05:00
jason.woltje caa4b59440 sessions: Sage, row 233 landing 57f59e0c3, 234 gate stop and r3, 240 briefed, 197/238 drift (sage-1742..1758) 2026-10-21 00:47:45 -05:00
jason.woltje 57f59e0c39 row 233: installer I3, stack-upgrade.sh and the host run sheet (#1735, Vikunja #432)
Lands Darkwing's round 1 candidate, manifest 464ce9eb177a (1a1dc80ca on darkwing's branch), approved by
Filbert (29922, rev 2659). Seven paths: scripts/stack-upgrade.sh (new), scripts/stack-release.sh,
docs/guides/host-run-sheet.md (new), docs/TOOLS.md, packages/cli/tests/stack-upgrade.test.mjs (new),
packages/cli/tests/stack-release.test.mjs and agents/darkwing/work/queue-233/BUILD.md.

Evidence: Darkwing's gate on d99166ade (base e65a3ab4d) 1201/0, mutants m1-m8 8 of 8 killed. The N1 fix
08db6ac3f (flock sleep leak in stack-upgrade.test.mjs) reran c0 20/0 with m1 killed (sage-1708); BUILD.md
doesn't record that rerun. Since e65a3ab4d no commit touches the seven paths. Rows 241 and 242 changed bus
and cli files. Only cli tests read the seven paths, so Sage ran the cli suite on 5465fd52d plus the seven
paths: 617 pass, 0 fail (out233/pkg-cli.log, 05:26:33Z to 05:35:00Z). A combined gate with row 234 failed bus schema.test.mjs:522 on 234's new tables, and
Gizmoduck found a second 234 failure (stack-release.test.mjs T46), so Sage stopped it. Row 234 round 3 fixes
both and rebuilds on this landing, since its T46 lines go in a file this commit changes.

Filbert's N1, N4 and N5 go to row 240 (brief b00d7d94d). N2 and N3 are in DEFERRED.md.
2026-10-21 00:35:18 -05:00
jason.woltje 5465fd52d5 sage: DEFERRED, checkCreative doesn't refuse credentials (row 213 r3 Webby N1, mos-1160) 2026-10-21 00:12:52 -05:00
jason.woltje b00d7d94d0 sage: row 240 (I4) carries Filbert N1, N4 and N5 from the 233 review; N2 and N3 go to DEFERRED (29922, sage-1727) 2026-10-21 00:01:26 -05:00
jason.woltje 642a9b87de sessions: Filbert, row 233 round 1 approve (29922, rev 2659) 2026-10-20 23:58:01 -05:00
jason.woltje 16019b80e6 sessions: Darkwing, row 233 in review round 1 (1a1dc80ca, manifest 464ce9eb, revs 2644 and 2647), with its BUILD-LOG entry 2026-10-20 23:51:01 -05:00
jason.woltje 9b53620185 sage: 10-24 window draft 0 counts grant look-alike events before --migrate (row 234 A22b, gizmoduck-0107) 2026-10-20 23:29:34 -05:00
jason.woltje 7662b04902 sage: 10-24 window draft 0 adds rows 232 (e65a3ab4d), 241 (5d4a61776) and 242 (5fb736b92, fc1cb856b); 199 and 210 evidence list due in draft 1 2026-10-20 23:24:00 -05:00
jason.woltje 04ce8dfd56 queue: revs 2639-2641 (row 242 Duckworth approve record, landing note and done) 2026-10-20 23:22:41 -05:00
jason.woltje fc1cb856b0 publish: row 242 follow-up docs, the git socket comments name the hold and the late record; BUILD.md residual and :311 cite (Duckworth N1, N2, N4, #1748)
Megavolt's c5a125f4b on megavolt/row242-docs, parent 5fb736b92 (the row 242
landing). Comments and .md only: git-socket.mjs changes comment lines at
:39-44 and :433-435, BUILD.md records N2 and N4. No test pins either path's
hash. Landed without a gate under the doc-only rule.
2026-10-20 23:17:14 -05:00
jason.woltje c0c4907af9 sessions: Sage, rows 241 (5d4a61776) and 242 (5fb736b92) landed, queue revs 2628-2641 2026-10-20 23:14:49 -05:00
jason.woltje 5fb736b92d publish: a retry after publish-timeout pushes once; the bus holds the decision until its push ends and records a late push (row 242, #1748, P19 440)
Megavolt's candidate 635c10924 on megavolt/row242, manifest 1fbfab6c (3 paths:
agents/megavolt/work/queue-242/BUILD.md, packages/bus/src/runtime.mjs,
packages/cli/tests/publish-socket.test.mjs). Duckworth approved (comment 29889, rev 2639).

Mutants M1 and M2, run by Megavolt on Sage's slot: both killed (publish-socket 8/1 each).
Sage's landing gate on 12b8538e0 (96bac588f plus these 3 paths): 15 packages 2826 pass 0 fail (bus 500, cli 594, seat 164), 10 scripts/test-*.sh suites 429 pass 0 fail plus 156 node tests in the queue suite, 03:59Z to 04:12:44Z

The 3 paths are unchanged on refactor since the candidate's base 8fede5fc6. Follow-ups:
Duckworth's doc notes N1, N2 and N4 go in a later doc-only commit; the late-record notice
to the run is Vikunja 440 comment 432. No live change.
2026-10-20 23:13:06 -05:00
jason.woltje 96bac588fb queue: revs 2628-2638 (row 241 approve record, landing note and done; row 232 landing note and done; row 233 brief re-pin and Darkwing claim; row 242 in review round 1, request 29888) 2026-10-20 22:47:48 -05:00
jason.woltje 5d4a617762 bus: row 241 one decision lands once; a broker test with two checkLand tickets on one decision, where the second recordLand refuses decision-consumed and records nothing (row 241, #1747, P19 441)
Megavolt's candidate 3e3ed303e on megavolt/row241, manifest e8266864 (2 paths:
agents/megavolt/work/queue-241/BUILD.md, packages/bus/tests/land.test.mjs). Tests and
packet only, no source change. Duckworth approved (comment 29870, rev 2628).

Sage's slot on bbf3b1ee8 (0444acd08 plus these 2 paths):
- mutants M1 (#spent recheck, broker.mjs:963), M2 (CAS old value, seat working.mjs:391),
  M3 (land-busy, cli git-socket.mjs:351): all killed
- full gate: 15 packages 2797 pass 0 fail (bus 500, cli 565), 10 scripts/test-*.sh suites 429 pass 0 fail plus 156 node tests in the queue suite, 03:21:13Z to 03:34:16Z

Row 232's landing e65a3ab4d sits between that base and this commit. Its 9 paths are
disjoint from these 2 and touch no bus code. No live change.
2026-10-20 22:34:30 -05:00
jason.woltje e65a3ab4d2 stack-release: --units external for root-owned units, with confirm, revert and gate --since; stack-copy copy --bundle and wrapper --create (row 232, #1734, installer I1)
Blobs of Darkwing's round 2 da2da2f6c on origin/darkwing/row232-r2 (base
8fede5fc6), the 9 manifest paths, manifest 7af3c943. Round 2 is a
rebase-only round over row 230 (c8bc5cf4c): packages/cli/README.md is a
clean 3-way merge with 232's hunk unchanged, and the other 8 paths keep the
round 1 hashes (manifest 7a3b4ea1). Filbert approve round 1 (comment 29829)
and round 2 (comment 29865).

Under --units user every command line and every byte of output is the
base's. Under external the script reads root's system units, starts and
stops none, stages a copy behind AWAITING-UNITS, and confirm gates root's
start on the bus unit's ActiveEnterTimestamp and launches list.

Mutants 17 of 17 killed (BUILD.md). Darkwing's landing gate on da2da2f6c,
03:07:15Z to 03:17:48Z: packages/cli 593 pass 0 fail, 10 scripts/test-*.sh
suites 429 pass 0 fail (plus 156 node tests in the queue suite). Refactor
moved to 0444acd08 since, three commits touching no manifest path. No live
change: the stack runs from copy 4429a104f, and --units user stays the
default.
2026-10-20 22:19:47 -05:00
jason.woltje 0444acd084 queue: revs 2618-2627 (row 232 round 2 approve; row 241 in review round 1, request 29868; row 234 and row 242 claimed)
Gizmoduck: row 234 claim (2618). Darkwing: row 232 rebase round 2 onto 8fede5fc6, in-progress and in-review with request comment 29860 (2619-2621). Filbert: row 232 round 2 record, approve, comment 29865 (2622). Megavolt: row 241 in-review round 1, request failed on the token (2623-2624), row 242 claim (2626). Sage: the row 241 request as jarvis and its resolve to comment 29868 (2625, 2627).
2026-10-20 22:06:29 -05:00
jason.woltje 052ba95c7c sessions: Filbert row 232 I1 review round 2, approve (#1734) 2026-10-20 22:01:43 -05:00
jason.woltje a52d64ed41 sage: 10-24 window draft 0 names the landed commits for rows 210 (f5753c0e8), 237 (d7d943199) and the PRD (0adf8ddfe) 2026-10-20 21:52:36 -05:00
jason.woltje 8fede5fc6a sessions: Sage, row 210 landed f5753c0e8, queue revs 2604-2617 2026-10-20 21:51:47 -05:00
jason.woltje c084d8d423 queue: revs 2604-2617 (row 210 done, landed f5753c0e8; rows 241 and 242 added and briefed, 241 claimed by Megavolt; row 234 briefed)
Sage: rows 241 and 242 added and briefed (#1747, #1748), the row 210 round 3 request as jarvis, the row 210 landing note and done move, row 234 queued to briefed. Gizmoduck: row 210 r5 start and in-review round 3 (2607-2611). Duckworth: row 210 round 3 record, approve (2614). Megavolt: row 241 claim (2606).
2026-10-20 21:50:59 -05:00
jason.woltje f5753c0e8d host access: agent host sudo through the credential store, host.run over broker.sock (row 210, #1707)
Blobs of Gizmoduck's r5 0485384e9 on origin/gizmoduck/row210-r5 (base
ad95c888c), the 23 manifest paths, manifest 688d38b9. Duckworth approve
round 2 (rev 2592) and round 3 (comment 29851, rev 2614). Round 3 is a
rebase-only round over row 237 (d7d943199): broker.mjs is a clean 3-way
merge (610af0c4), the 210 hunks sit away from 237's insert, and range-diff
against r4 c43008749 is all equal.

Mutants 52 of 53 killed on the merged tree. M22b is equivalent (rev 2584);
its coverage moves to row 235. Preflight singles: seat sandbox 10/0, bus
project-runtime 3/0. Gate on ad95c888c plus these paths: 15 package suites
2796 pass 0 fail, 10 scripts/test-*.sh suites 429 pass 0 fail (plus 156 node
tests in the queue suite). Refactor moved to 9d165be8c since, two briefs
touching no manifest path. No live change: the stack runs from copy
4429a104f until the 10-24 re-pin window.
2026-10-20 21:47:11 -05:00
jason.woltje 9d165be8c7 plans: row 237 follow-up N1 brief, a retry after publish-timeout pushes once (duckworth-0069, P19 440) 2026-10-20 20:54:33 -05:00
jason.woltje c25785d3d5 plans: row 237 follow-up brief, one decision lands once, tests at the broker (megavolt-0069, sage-1651, P19 441) 2026-10-20 20:52:31 -05:00
jason.woltje ad95c888c3 sessions: Sage, row 237 and PRD 1.7+1.8 landed, queue revs 2540-2603 2026-10-20 20:49:55 -05:00
jason.woltje 584e2bd827 queue: revs 2540-2603 (rows 199, 200, 230, 237 done; 210 r4 and 238 in review; 232 in review round 1 approve; 233 briefed; rows 239 and 240 added)
Sweeps 64 uncommitted revs. Seats: sage, duckworth (2540, 2581, 2592), gosalyn (2550-2551, 2558-2560),
megavolt (2555, 2574, 2576-2577), webby (2557, 2563, 2599), launchpad (2570, 2590-2591),
gizmoduck (2585-2587), darkwing (2595-2596), filbert (2600).
Landings: 200 9e1085816, 199 cb5fdb064, 230 c8bc5cf4c, 237 d7d943199.
2026-10-20 20:49:45 -05:00
jason.woltje 0adf8ddfe2 prd: Mosaic Stack PRD versions 1.7 and 1.8, status approved version 1.8 (DECISIONS Q192, decision d52febb5, Vikunja #434)
1.7 is PRD-1.7-DRAFT.md (sha256 e896746d), approved by decision d52febb5 (prd.approve, yes). 1.8 is r7
82df17347 (sha256 de079281), approved by Jason in T3, relayed by Mos (mos-1154, DECISIONS Q192), so it has no
in-Stack prd.approve. packages/tasks/requirements.json moves to version 1.8 with 68 ids: COS-3, CRED-3, CRED-4,
GRANT-1 to 4, TRUST-1, TRUST-2 and LAND-2 are new. The tasks and bus requirement pins move to 68 and 1.8.
Written by Honker (honker/prd-17-18 2884ee496), reviewed by Duckworth (duckworth-0075, -0076, -0077).
2026-10-20 20:46:55 -05:00
jason.woltje d7d9431990 bus: one decision, one open publish; checkPublish refuses publish-busy while another ticket holds the decision (row 237, #1743)
The decision is spent only at recordPublish, after the push, so two checkPublish calls on one decision could
both push and land the commit on two fork refs. checkPublish now refuses publish-busy while an open ticket
holds the same business and decision. A refusal drops the ticket and leaves the decision unspent. Broker test
and a socket race test with exactly one git-receive-pack. Residuals 1-3 in BUILD.md; residual 3 (checkLand)
is a follow-up row.

Built by Megavolt (megavolt/row237 e42415935, duckworth-0068 N1), reviewed by Duckworth (approve, comment
29810, manifest bb777cb0). Sage's gate on 816d48b03 plus these paths: M1 killed, every package suite and every
scripts/test-*.sh green.
2026-10-20 20:45:08 -05:00
jason.woltje a0e3d7cfdd plans: installer I4 hostcheck row brief, split out of I3 (darkwing row 233 G2, sage-1650) 2026-10-20 20:38:37 -05:00
jason.woltje 9d24ff9b28 sessions: Filbert row 232 I1 review round 1, approve (#1734) 2026-10-20 20:34:13 -05:00
jason.woltje 6c04b89dae plans: row 194 follow-up brief, the revoke under the writer's lock and a fire test (duckworth-0074, mos-1158) 2026-10-20 20:29:43 -05:00
jason.woltje 6f7dacb417 sage: 10-24 window draft 0 carries PRD 1.7+1.8 (mos-1154, mos-1156, DECISIONS Q192); the live pm cites 1.6 until the window 2026-10-20 20:28:39 -05:00
jason.woltje d21b2615c1 darkwing: row 232 in review round 1 (#1734), BUILD-LOG and SESSIONS lines 2026-10-20 20:28:11 -05:00
jason.woltje 816d48b03e sage: row 42 run log, Jason's pre-S7 checks (mos-1154); PRD 1.8 r7 raise dropped, Jason approved in T3 (DECISIONS Q192, NOTE.md) 2026-10-20 20:22:19 -05:00
jason.woltje 011d41ba6a sage: row 42 S7 dates in the run log (sage-1622, mos-1148 C1 C2, Vikunja 442), 10-24 window draft 0 (mos-1145; NEW open), Vikunja scripts and records: 412/424 landing comments, 237 follow-ups 440/441, P19 405-408 and 411 re-plan comments 416-420, read-tasks.mjs (#1524) 2026-10-20 20:06:39 -05:00
jason.woltje c8bc5cf4cb case2: unit render and host precheck, case2-unit.sh and case2-precheck.sh (row 230, #1732)
Squash of Gosalyn's 96768766c on gosalyn/c2-13a-r2 (base 722136772), the
5 manifest paths, build.patch 56649f9a, manifest fbb0d7e42c67 (Webby
approve round 2, rev 2563). Gosalyn's gate on 96768766c: cli 541 pass
0 fail; scripts/test-*.sh 429 pass 0 fail (summed by script); mutants
10 of 10 killed. packages/cli/README.md is a 3-way merge with row 199
(cb5fdb064): the hunks don't overlap (line 710 vs 1022). host.test.mjs:855
is the only test that reads that file. On cb5fdb064 plus the patch,
case2-unit.test.mjs 30/0 and host.test.mjs 42/0. Refactor moved to
390ea5261 since, a README fix touching no manifest path. The script
runs on a host only in a separate window with Jason's go.
2026-10-20 19:36:22 -05:00
jason.woltje 390ea5261c business: README names nine hard waits, git.push.remote included; row 199 added it to GATED_ONLY (row 199 review N3, duckworth-0068, #1697)
Megavolt's 5d7e4d26e on megavolt/n3-readme, from cb5fdb064. Doc-only: no
test or scripts/test-*.sh reads packages/business/README.md. The text
matches packages/business/src/vocabulary.mjs, where GATED_ONLY has nine
actions.
2026-10-20 19:29:22 -05:00
384 changed files with 48462 additions and 563 deletions
+13
View File
@@ -5174,3 +5174,16 @@ Before: managed project/thread verbs had no Console conversation area. After: fi
2026-10-20 04:44:54 UTC | Row 178 round 2 preparation | Before: Dewey C1 five render guards lacked killing tests. Added independent refusal fixtures, a temporary uid+1 id stub with cleanup, and empty loopback proof case; activation ordering recorded in r2 BUILD. Product unchanged; syntax/text preflight only, no test or mutant execution. Pending row 181 rebase and Sage slot.
2026-10-20 05:10:34 UTC | Row 178 #1676 round 2 verified | Before: C1 render refusals lacked killing tests and row 181 base was pending. Rebased onto 931651f2d, kept all COUNTED/count code and tests, appended T178 tail; added independent owner/collision/absolute/charset/header and empty-loopback proof tests. Four files singly 156/156, all 24 mutants killed, metadata and sandbox fixture probes pass; scratch sweep empty. Packet agents/rocko/work/queue-178/r2/, candidate 9300a670, inventory d0dcde7b (59/59), scan 58 files no findings; in-review revs 2166-2167, expected no-token request failure. No live actions or commits.
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-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.
+7 -3
View File
@@ -86,7 +86,8 @@ The layers each adapter relies on, and what they don't cover, are in
### A role's skills (row 64)
A managed session's skills reach both adapters as text in the bundle's
`prompt.md`, which they pass with `--system-prompt`. Neither adapter loads a
`prompt.md`, which they pass as a file: `pi` with `--system-prompt <path>`
and `claude` with `--system-prompt-file <path>` (row 213). Neither adapter loads a
skill of its own: `pi` keeps `--no-skills`, since role sessions never get
`MOSAIC_SKILLS`, and `claude` keeps `--disable-slash-commands` and
`--restricted`. Sage chose this on 2026-10-19 from a probe, recorded in
@@ -104,5 +105,8 @@ skill of its own: `pi` keeps `--no-skills`, since role sessions never get
The cost is prompt size: a role's skill text is in every turn, where the
other routes load a skill when it's used. The bundle caps a role's skills at
64 KiB together, since `claude` passes `prompt.md` as one argument and Linux
caps one argument at 128 KiB.
64 KiB together. The cap dates from when `claude` passed `prompt.md` as one
argument, which Linux caps at 128 KiB; row 213 passes the file instead, so no
local user reads the prompt in `/proc/<pid>/cmdline`. Pi 0.85.1 uses the path
string itself as the prompt when the file is missing or can't be read, so the
pi adapter exits 2 unless the path is a readable regular file.
+10 -3
View File
@@ -27,6 +27,11 @@ need MOSAIC_CLAUDE_MCP_CONFIG
for f in "$MOSAIC_SYSTEM_PROMPT_FILE" "$MOSAIC_CLAUDE_SETTINGS" "$MOSAIC_CLAUDE_MCP_CONFIG"; do
[ -r "$f" ] || { echo "claude adapter: not readable: $f" >&2; exit 2; }
done
PROMPT_FILE=$MOSAIC_SYSTEM_PROMPT_FILE
case "$PROMPT_FILE" in
/*) ;;
*) PROMPT_FILE="$(pwd)/$PROMPT_FILE" ;;
esac
[ "${MOSAIC_INTERACTIVE:-}" != "1" ] || { echo "claude adapter: interactive mode isn't supported" >&2; exit 2; }
CLAUDE=claude
if [ -n "${MOSAIC_CLAUDE_BIN:-}" ]; then
@@ -56,7 +61,10 @@ fi
# Flags:
# -p one-shot: print the answer and exit
# --output-format text stdout is the answer only
# --system-prompt the generated prompt replaces the default
# --system-prompt-file the generated prompt replaces the default, given
# as a file so no local user reads it in
# /proc/<pid>/cmdline (row 213); absolute, since
# the cd above changed the working directory
# --effort the instance's thinking var (row 65; 2.1.302 takes
# low, medium, high, xhigh, max)
# --restricted no user/project/local settings files; --settings
@@ -76,10 +84,9 @@ fi
# text in the system prompt (row 64)
ALLOWED=mcp__mosaic
[ -z "${MOSAIC_TOOLS:-}" ] || ALLOWED="$MOSAIC_TOOLS,mcp__mosaic"
PROMPT_CONTENT="$(cat "$MOSAIC_SYSTEM_PROMPT_FILE")"
"$CLAUDE" -p "$MOSAIC_REQUEST" \
--output-format text \
--system-prompt "$PROMPT_CONTENT" \
--system-prompt-file "$PROMPT_FILE" \
--model "$MOSAIC_MODEL" \
--effort "$MOSAIC_THINKING" \
--restricted \
+14 -4
View File
@@ -8,7 +8,17 @@
set -eu
[ -n "${MOSAIC_SYSTEM_PROMPT_FILE:-}" ] || { echo "pi adapter: MOSAIC_SYSTEM_PROMPT_FILE is required" >&2; exit 2; }
[ -r "$MOSAIC_SYSTEM_PROMPT_FILE" ] || { echo "pi adapter: system prompt not readable: $MOSAIC_SYSTEM_PROMPT_FILE" >&2; exit 2; }
# Row 213 (C2-08): pi gets the prompt as a path, so no local user reads it in /proc/<pid>/cmdline.
# Pi 0.85.1 reads --system-prompt as a file only when the path exists and reads, and otherwise
# uses the path string itself as the prompt, without a warning for a missing file
# (dist/core/resource-loader.js resolvePromptInput). So the path must be a regular readable
# file here, and is made absolute before the cd below, since pi resolves it from its own cwd.
PROMPT_FILE=$MOSAIC_SYSTEM_PROMPT_FILE
case "$PROMPT_FILE" in
/*) ;;
*) PROMPT_FILE="$(pwd)/$PROMPT_FILE" ;;
esac
[ -f "$PROMPT_FILE" ] && [ -r "$PROMPT_FILE" ] || { echo "pi adapter: system prompt not readable: $MOSAIC_SYSTEM_PROMPT_FILE" >&2; exit 2; }
# MOSAIC_AGENT_NAME is optional in headless mode (identity section is then
# omitted); interactive launches always set it via scripts/agent.sh.
@@ -94,7 +104,8 @@ fi
# All flags documented in the pi package README (CLI Reference):
# -p/--print one-shot mode: print the response and exit (omitted in
# interactive TUI mode)
# --system-prompt replace the default prompt with the generated one
# --system-prompt replace the default prompt with the generated one, given
# as the file's absolute path (checked above), never its text
# --no-* no ambient context/skills/extensions/templates/themes
# EXT_FLAGS the explicitly provided extensions only (-e)
# SESSION_FLAGS ephemeral | persistent | forked (per env)
@@ -103,7 +114,6 @@ fi
# --offline no startup network operations (update checks/telemetry)
# --no-approve ignore project-local .pi/ files (settings, SYSTEM.md)
# whatever trust is saved for the workspace
PROMPT_CONTENT="$(cat "$MOSAIC_SYSTEM_PROMPT_FILE")"
set -- \
--offline \
--no-approve \
@@ -118,7 +128,7 @@ set -- \
--provider "$PI_PROVIDER" \
--model "$PI_MODEL" \
$THINKING_FLAG \
--system-prompt "$PROMPT_CONTENT"
--system-prompt "$PROMPT_FILE"
# One-shot mode appends -p and the request (both safely quoted);
# interactive mode appends nothing - clean TUI.
[ "$INTERACTIVE" = "1" ] || set -- "$@" -p "$MOSAIC_REQUEST"
+161
View File
@@ -0,0 +1,161 @@
# Row 195 build: `scripts/case1-accept.sh`
Row 195 (#1693), the Case 1 install on 10.1.1.53. Design r7 at 5425ea2f3, approved by Duckworth
(duckworth-0087, `agents/duckworth/work/row195/REVIEW-r7.md` at 58d930f89; Gitea 29962). Sage's go to build
is sage-1817.
## What this commit holds
- `scripts/case1-accept.sh` (new, 100755): section 5's acceptance, F1-F11 and P1-P11.
- `packages/cli/tests/case1-accept.test.mjs` (new): section 5's cases.
- `docs/TOOLS.md`: the `case1-accept.sh` entry after the hostcheck entry.
- This file.
- `DESIGN.md`, doc-only fixes Sage named in sage-1817, with no new design round:
- Duckworth's N1. Section 1's human-id row cited `broker.mjs:245`; the check is `broker.mjs:321`.
- Row 226's state at :21 and :326. It said "unclaimed"; 226 is assigned to Gosalyn, briefed and not
started (sage-1814).
- :340 said I'd send the section 6 items to Gosalyn once 226 was claimed. I sent them on 2026-10-21
and Gosalyn acknowledged (gosalyn-0085), so the line now says that.
Nothing else changes. No `case2-*.sh`, `stack-upgrade.sh`, `stack-release.sh`, `case1-hostcheck.sh` or run
sheet edit, and nothing touches .53 (sage-1809).
## Deviations from section 5, and choices it left open
1. P4's mark is the single token `accepted Q182+Q183`. Section 5 lists `accepted Q182` and `accepted
Q183` as marks and gives P4 both. One line can carry one mark, and P4 is the only Q182 path, so the
count list has `accepted Q182+Q183` in place of `accepted Q182`.
2. The test seam replaces `getent`. Under `--root`, the script reads `DIR/etc/passwd` and `DIR/etc/group`
with awk. Without it, the same helper calls `getent`. The groups of a user are its primary gid, then
each group whose member list names it, which is what `id -G` prints. So there is no fake `id`, and
the script never calls `id` for another user.
3. P10 reads the port floor from `/proc/sys/net/ipv4/ip_unprivileged_port_start` rather than calling
`sysctl`, so the seam covers it the way hostcheck's item 7 is covered. There is no fake `sysctl`.
4. There is no fake `bwrap`, `claude`, `node` or `hostcheck.sh` on PATH. The script runs each by the
absolute path the args file or the install names, so the test puts stubs at those paths under
`--root`, and the fake `sudo` runs the stack's user's commands from the tree.
5. The args file must say `--profile factory` and name `--business`. Both are new refusals beyond
`stack-upgrade.sh`'s rules. A library file, or one for another business, would make F4 and F7
check the wrong thing. After `--`, `--port` and `--tester-port` must be numeric. The args file is the
only source of the ports.
6. F8, F9 and F11 read file metadata as root: `stat`, `-L` and `find -maxdepth 1 -name '*.json'`. None
opens the credential. F9 parses `managed-settings.json` with the pinned node, run by root: that node
and that file are both root's own, installed by ops-01.
7. F7 reads `launch.by` from `$HOME/.config/mosaic-dev/businesses/<id>.json` as the stack's user. That
path is `businessFilePath(id)` under the default config, which is what the unit uses. It doesn't load
the copy's `business.mjs` as `stack-release.sh`'s `plugins launchby` does. The user runs the whole
check and prints one line, and root accepts only `ok <instance> run <run>` (sage-1653: root doesn't
parse the user's output beyond a fixed pattern).
8. F5 names a process claude when its argv0's basename or its `comm` starts with `claude`. The window
confirms this against a real role session. If the bwrap'd claude shows another name, F5 fails closed
and the fix is this match, not the label check.
9. F3 and F5 run together. `talk` goes out in the background under `timeout 330`, and the `/proc` scan
repeats every half second until it finds a labelled pair or `talk` exits. F3's line prints first, then
F4, then F5, so the output keeps id order. F3 passes on exit 0 with the `sent` line and a reply line,
both counted on stdout only (`cli.mjs:395` and the `formatMessage` write); talk's error goes to stderr.
12. `--claude` must match `^/[A-Za-z0-9/._+@-]+$`. The `@` is for `node_modules/@anthropic-ai/...`;
`stack-upgrade.sh` doesn't check `--claude` at all.
10. F1 requires all four uids in `/proc/<MainPID>/status` (real, effective, saved, filesystem) to be the
user's.
11. Evidence text the stack's user controls (talk's last line, the precheck's last line, F6's and F10's
`--version`) is cut to printable ASCII, one line, 200 bytes.
## Tests
`packages/cli/tests/case1-accept.test.mjs`, 22 tests. Every case builds a scratch host with every F check
passing and every P check closed or accepted, then changes one thing:
- all pass: exit 0, ids in order, each P mark, the count lines match the check lines, and the stand-in
credential's value appears nowhere;
- each F check failing alone exits 1 with only its line failing;
- F3 on exit 0 with only the `sent` line, and its evidence taking talk's stderr;
- F1 on a failed unit and on `User=root`; F2 on healthz failing and on another pid holding the port;
- F5 on `unpriv_bwrap (enforce)`, `bwrap//&unpriv_bwrap (complain)`, a trailing space on either label,
no bwrap of the user's, and passing on `comm` alone;
- F6 on a stub that prints nothing and on a path outside `claude-<pin>`;
- F7 on two live PM launches, a live one for another business, and no `launch.by`;
- F8 on 0640, on another owner and on a symlink, never printing the credential;
- F9 on a group-writable mode, another owner and a number for the flag;
- F11 on one `*.json` under `discord/`, and passing on an empty directory;
- each P check's unnamed mark, with the line count each fault makes: P6 one line per member, P9 one per
missing item; P2, P5, P10 and P11 in more than one way;
- every P check unnamed at once still exits 0;
- nine bad args files refuse with exit 2 before any command runs, each with its own refusal text;
- usage errors, and a user's run without `--root`, exit 2;
- every in-instance call goes through `sudo -u mosaic -H env PATH=<node dir>:/usr/local/bin:/usr/bin:/bin`,
read from the fake `sudo`'s argv, and root's other `sudo` calls are exactly `-l -U mosaic`,
`-l -U localadmin` and the jarvis bwrap probe;
- the host check records `--check` and never `--enforce`.
## Mutants
Section 5's twelve, each one exact replacement in `scripts/case1-accept.sh`, run by
`/mnt/storage/scratch/darkwing-195-out/mutants.py` in Sage's slot (sage-1865). All twelve were killed.
After each one the script's bytes were restored, and the final sha256 matched (`d434f15ff638...`). The
killers below are the tests that failed in each mutant's saved log.
| # | Mutant | Killed by |
|---|---|---|
| 1 | F1 passes on a MainPID owned by root | each F alone |
| 2 | F2 accepts a listener on 0.0.0.0 | each F alone |
| 3 | F3 passes without a reply | each F alone; F3 exit 0 with no reply line |
| 4 | F5 matches a substring | F5 labels (the trailing space) |
| 5 | F6 skips the `--version` check | each F alone; F6 stub and path |
| 6 | F8 skips the owner check | F8 0640, owner and symlink |
| 7 | F11 ignores `discord/` | each F alone; F11 `*.json` |
| 8 | P1 closes on any output | P unnamed; every P unnamed |
| 9 | P5 maps to `closed` with jarvis in docker | P unnamed; every P unnamed; P2 and P5 names docker |
| 10 | P9 drops the jarvis item | P unnamed; P9 per item |
| 11 | P11 accepts any `Before=` | P unnamed; every P unnamed |
| 12 | a P check's `unnamed` sets the exit to 1 | P unnamed; every P unnamed; P10 and P11; P2 and P5; P9 per item |
Mutant 1 survived the F1 variants test (a failed unit, `User=root`), because those faults fail F1 on the
unit's state and `User=`, not on the uid line. Each F alone covers the uid line.
## Pre-slot read review
Since every test run is slot work, a read-only subagent traced each case through the script before the
first slot run, executing nothing. It found four defects, all fixed in this commit:
- The `--claude` regex had no `@`, so every run refused the args file, the real .53 one included.
- The fake `stat` logged the args-file call, so the bad-args test's "no command ran" check would fail.
- The P5 and P6 faults each rewrote `etc/group` from the constant, so applying both lost P5's change.
- F3 counted stderr into its line count, so a mutant dropping the exit check survived. F3 now counts
stdout only, and a `sent`-only case covers the line count.
## Run environment
The build commit ddf7dce6c sits on base 4ced5e63d (origin/refactor when it was pushed). It was first built
on c515273db as 4a51d392c. Row 238 then changed `docs/TOOLS.md`, so I cherry-picked the three commits onto
4ced5e63d as `darkwing/row195-b`. `git range-diff` shows all three identical. node v26.8.1 on dragon-lin,
every run behind my busy check and Sage's, chained with `&&`.
- Step 1, sage-1829 (07:41:54Z to 07:42:05Z), TMPDIR `/tmp/dw195`:
`node --test packages/cli/tests/case1-accept.test.mjs`, 22 pass, 0 fail, 11.0 s.
- Step 2, sage-1865 (08:34:27Z to 08:36:21Z), TMPDIR `/mnt/storage/scratch/tmp/r195`: the twelve mutants
above, all killed.
- Step 3, the gate (08:36:21Z to 08:47:06Z), same TMPDIR, `COMPOSE_PROJECT_NAME=darkwing-r195`:
| Suite | Result |
|---|---|
| packages/cli (`node --test tests/*.test.mjs`) | 662 pass, 0 fail, 0 skipped |
| test-auth.sh | 15 passed, 0 failed |
| test-conductor.sh | 17 passed, 0 failed |
| test-config.sh | 31 passed, 0 failed |
| test-discord.sh | 78 passed, 0 failed |
| test-extension-package.sh | 18 passed, 0 failed |
| test-foundation.sh | 44 passed, 0 failed |
| test-land-work.sh | 86 passed, 0 failed |
| test-queue.sh | 156 node pass, 0 fail; 28 passed, 0 failed |
| test-release.sh | 14 passed, 0 failed |
| test-task.sh | 98 passed, 0 failed |
Summed: 818 node tests passed with 0 failed, and 429 selftests passed with 0 failed. The compose network
was removed afterwards, and the sweep found no process left in the worktree, the output directory or the
TMPDIR. This file's results are a doc-only commit on top of the build, so nothing that ran has changed.
## Correction to deviation 6 (follow-up, 2026-10-21)
Deviation 6 says the node F9 runs is root's own. At 37171f65b the script didn't check that. F9 ran
`--node` as root before F10's path check, so the header's "root itself only reads" was false
(Duckworth's N1, `agents/duckworth/work/row195/REVIEW-m1.md` at 6afcee2a5). A host installed as designed
was safe, because `case2-unit.sh` refused a non-root-owned `--node`. Row 245 carries the fix and its
tests: `agents/darkwing/work/queue-245/BUILD.md`.
+388
View File
@@ -0,0 +1,388 @@
# Row 195 design: the Case 1 install on 10.1.1.53 (r7)
Darkwing, 2026-10-21. Brief: `docs/plans/2026-10-20_hub-own-uid.md`, blob 45be7f09afc1, section "Brief: the
Case 1 install on 10.1.1.53 (row 195)". Gitea #1693. Reviewer Duckworth, gate owner Sage. Branch
`darkwing/row195-r7` on c515273db. r6 (b7239bb66 on `darkwing/row195`, approved duckworth-0055) is the base,
carried unchanged as 7398b7dd7. The r6 text is in that commit and in b7239bb66.
## Changes from r6
Q187 (mos-1108) made install and upgrade product commands, and mos-1118 split r6 into the installer rows
(`docs/plans/2026-10-20_installer-rows.md`). That brief says row 195 "shrinks to the factory profile's
values and the Case 1 acceptance". So r7 is mostly a cut. What r6 designed now ships elsewhere:
| r6 piece | Now | State |
|---|---|---|
| `stack-release.sh --units external`: `confirm`, `revert`, `gate`, `rollback --to`, external `adopt` and `status`; `stack-copy.sh check --units external` and bundle copies | row 232, I1 | landed |
| `case1-repin.sh` (A1-A5, A2f, R1-R4) | `scripts/stack-upgrade.sh`, row 233, I3 | landed |
| the host steps 2a, 2a', 2a'' and the window's root steps | `docs/guides/host-run-sheet.md` sections 1-10, row 233 | landed |
| `case1-hostcheck.sh`, `case1-pins.sh`, `mosaic-hostcheck.service` (the drift stop, r6 B2) | row 240, I4 | landed c515273db |
| the unit render, `--profile factory`, `--pm`, `--claude`, the factory serve flags, the precheck | row 230, C2-13a, I2 | landed |
| the install as `mosaic`: bootstrap, business file, copy | row 226, C2-13b, I2, `scripts/case2-install.sh` | assigned to Gosalyn, briefed, not started |
| `docs/guides/case1-install.md` | the host run sheet, plus section 6 of this file | |
Other changes:
- Claude moves under `/opt` (sage-1522, run sheet section 3). `--claude` is
`/opt/mosaic-stack/claude-<pin>/node_modules/@anthropic-ai/claude-code/bin/claude.exe`, root's, and root
installs and upgrades it. r6's `/srv/mosaic/.local/bin/claude` is gone. F6 reads the new path.
- The args file is `/etc/mosaic-stack/dev.args` (run sheet section 7), not `case1-unit.args`. It holds
`--copy` too, and `stack-upgrade.sh` rewrites only that line, after confirm passes.
- Both units are `WantedBy=multi-user.target` and start at boot (Q189, mos-1122). r6's "the Console has no
`[Install]`, root starts it by hand after a reboot" no longer holds.
- The business id is stated, with the DEFERRED 240 N1 reasoning (sage-1809, sage-1813). Section 7.
- The window can't run until row 226 lands. Sage added 226 to row 195's after list at rev 2718
(sage-1813). Section 6 lists what row 195 needs from 226's install.
- The acceptance runs as root through ops-01, not as `localadmin`. `aa-status`, `sudo -l -U`,
`systemd-run --uid` and the hostcheck all need root. It gains four functional checks for items the run
sheet marks "precheck line missing" (F8-F11) and one host line (P11). Section 5.
- The Discord row in section 2 changed. The precheck's "no Discord binding" line is C2-13b's and is
library-only, so for the factory it's F11 in the acceptance.
What row 195 still builds: `scripts/case1-accept.sh` and its test (section 5). The rest of this file is
inputs, the window order and the record the reviewer checks.
## 0. What I checked on the host
Read only, as `localadmin` over ssh, 2026-10-20 between 20:40Z and 21:51Z. Nothing in r7 touched .53
(sage-1809). The window's first step re-reads these, since they can drift in a day: unattended-upgrades
runs daily.
| Item | 10.1.1.53 on 2026-10-20 | Needed |
|---|---|---|
| OS | Ubuntu 24.04.5, kernel 6.8.0-124, x86_64, 12 cores, 94 GiB, 217 GiB free on `/` | fits |
| `mosaic` user, group, `/srv/mosaic` | absent | run sheet section 1 |
| node | `/usr/local/bin/node` v22.22.2, root-owned | 24 or later, under `/opt` (run sheet section 2) |
| claude | not on localadmin's PATH | under `/opt` (run sheet section 3) |
| bwrap | absent; apt candidate `bubblewrap 0.9.0-1ubuntu0.3` | run sheet section 4 |
| user namespaces | `kernel.apparmor_restrict_unprivileged_userns = 1`, apparmor 4.0.1, no bwrap profile loaded | run sheet section 4 |
| profiles that name bwrap | none (`grep -rlw bwrap /etc/apparmor.d` exits 1) | hostcheck item 3 |
| `fs.protected_hardlinks` | 1 | hostcheck item 6 |
| `net.ipv4.ip_unprivileged_port_start` | 1024 | run sheet section 5 |
| listeners below 1024 | 22 (sshd) and 53 (the resolver); 732 is free | the Console's port |
| ports 7330-7333 | free | 7333 is the tester port |
| `localadmin` | NOPASSWD sudo (#423 step 1, mos-1066, mos-1077) | ops-01's root path |
| `jarvis` | groups sudo, docker, lxd, adm | #423 step 4 removes them (P5) |
| unattended-upgrades | on, with the security pocket, which carries bubblewrap | the drift stop (row 240) |
| journald | `SplitMode=uid` | the PM check reads `launches list`, not the journal (row 232) |
## 1. The factory profile's values
Instance `dev`, row 190's default (mos-1118 choice 4). `~` is `mosaic`'s home.
| Input | Value | Who gives it |
|---|---|---|
| user | `mosaic`, primary group `mosaic` with no other member | fixed |
| home | `/srv/mosaic`, 0700 | fixed |
| config dir | `/srv/mosaic/.config/mosaic-dev` | row 190 |
| data root | `/srv/mosaic/.mosaic-dev`, marker `{"instanceVersion":1,"name":"dev"}` | row 190 |
| business | `mosaic-stack`, file `<configDir>/businesses/mosaic-stack.json` | fixed (section 7) |
| profile | `factory` for `case2-install.sh`, `case2-unit.sh` and `case2-precheck.sh` | fixed |
| roles | the factory set from `roles/`, with a chief that has a harness var | the go names the set (section 2) |
| human id | Jason's, not `tester` (`broker.mjs:321`) | the go |
| model credential | `<configDir>/secrets/claude-session.env`, `mosaic` 0600, one line `CLAUDE_CODE_OAUTH_TOKEN=...` | Jason places it (section 3) |
| token slot path | none | section 2 |
| node | `/opt/mosaic-stack/node-<v>/bin/node`, `<v>` 24 or later | the go names `<v>` and its sha256 |
| claude | `/opt/mosaic-stack/claude-<pin>/node_modules/@anthropic-ai/claude-code/bin/claude.exe`, `<pin>` 2.1.304 unless the go names a newer one | the go |
| PM | on (`--pm`) | fixed |
| Console | `--port 732` on 127.0.0.1, with the unit's `CAP_NET_BIND_SERVICE` | fixed |
| tester | `--tester-port 7333` on 127.0.0.1 (P10) | fixed |
| board | none | fixed |
| release | a git bundle of `refs/heads/refactor` and the 40-hex commit (run sheet section 6) | the go names both and the bundle's sha256 |
The args file. `/etc/mosaic-stack/dev.args`, `root:root` 0644, one argument per line (run sheet section 7):
```
--profile
factory
--user
mosaic
--home
/srv/mosaic
--business
mosaic-stack
--copy
<40-hex commit>
--node
/opt/mosaic-stack/node-<v>/bin/node
--claude
/opt/mosaic-stack/claude-<pin>/node_modules/@anthropic-ai/claude-code/bin/claude.exe
--pm
--
--port
732
--business
mosaic-stack
--tester-port
7333
```
`case2-unit.sh` takes exactly these before `--` (`scripts/case2-unit.sh:54`), and `stack-upgrade.sh`
refuses `args-unreadable` on anything else (`scripts/stack-upgrade.sh:126-148`). The factory profile
refuses the preview trio, `--send` and the proof pair (`case2-unit.sh:169`). .53 has no proxy, and Jason
reaches the Console with `ssh -L` (sage-1451 Q3). Changing a serve flag is a re-install with its own go,
not an upgrade.
Why the port is 732 (r6, mos-1101 item 5). Role sessions share the host's network namespace
(`packages/seat/src/sandbox.mjs:67`), so while the Console is down a session could bind a port above the
floor and answer in its place. Below 1024 only the Console unit can bind, through its capability. The
tester port can't go below 1024 (`packages/webui/src/cli.mjs:33`), so P10 records it.
PATH for `mosaic`'s steps. `sudo -u mosaic -H` resets PATH to sudo's `secure_path`, which finds
`/usr/local/bin/node`, v22. So every `mosaic` step runs as
`sudo -u mosaic -H env PATH=/opt/mosaic-stack/node-<v>/bin:/usr/local/bin:/usr/bin:/bin ...`, the list
`case2-unit.sh` renders as the units' PATH. `stack-upgrade.sh` builds it from the args file's `--node`.
## 2. Where Case 1 differs from Case 2
| Case 2 (library) | Case 1 (factory) | Reason |
|---|---|---|
| business file with the library mapping | `mosaic-stack` with no `library` key. The precheck refuses a library project under `factory` (`profile-library-business`, `case2-precheck.sh:127-128`) | the factory has no books repo |
| `bean`, `chet`, persona vars and files, specialists | the factory roles from `roles/`, with a chief that has a harness var | the go names the set. I haven't read Jason's live business file and won't. The acceptance needs only the chief (F3); a chief with no harness var answers `no-route`. The human adds roles later with `instances.set`, no new row |
| token slot `library-gitea` | none | the factory's per-agent git identity is row 199's credential store (Megavolt, #1697). Until it lands, git work stays on dragon-lin (Q182) |
| `bus.tester` off | on; the precheck refuses it off under `factory` (`case2-precheck.sh:135`) | the acceptance and the Q173 test system need the tester |
| persona modes and digests, `library.sync`, `library.status` | skipped | no persona files, no library (C2-13b brief, `factory` profile) |
| `ZAI_API_KEY` in `claude-session.env` | not checked; the file holds `CLAUDE_CODE_OAUTH_TOKEN` | no instance has provider `zai` (run sheet section 9) |
| no Discord binding (C2-13b, library only) | none either. F11 checks that `<dataRoot>/discord/` holds no binding | the factory's connector stays on dragon-lin. Two connectors on one bot token would split its events |
| the claude marker prompt | runs, since the chief's harness is claude-code | |
| serve flags: preview trio, `--send`, proof pair | `--port 732`, `--business mosaic-stack`, `--tester-port 7333` | no proxy on .53 |
| bus: no `--pm`, no `MOSAIC_CLAUDE_BIN` | `--pm`, and `Environment=MOSAIC_CLAUDE_BIN=<--claude>` | the PM dispatches the factory's work; without the path the bus finds no claude on the unit's PATH |
| Console proof file (run sheet section 9, .54) | none | no proof pair |
All of these are inputs to the landed scripts. Row 230 implemented the four factory items r6 asked for
(serve flags, `--pm`, `--claude`, row 190's tokens), and nothing in them hardcodes a Case 1 value.
## 3. Model access
Q185: Case 1 uses the account the factory uses today, the Claude subscription behind dragon-lin's
`claude-session.env`. No script copies that file and none reads its value. In the window, Jason, or the
person he names under the Q173 secrets rule, makes a credential for .53 from that account and writes it
to `/srv/mosaic/.config/mosaic-dev/secrets/claude-session.env` as `mosaic`, mode 0600 (run sheet
section 9). Nobody prints it. The check is `stat -c '%U %a %F' PATH` only (F8).
The bus unit reads it through `EnvironmentFile=-` (`case2-unit.sh:243`). The leading `-` means a missing
file doesn't stop the bus, so the chief's first launch would fail instead. The window places the file
before the units start (section 6, W9), and the precheck's claude marker prompt then proves it works
without printing it.
Quota: the factory on dragon-lin and on .53 share one subscription. Q185's "use existing" accepts that.
The pi harness stays off on .53 until P19 #383 settles (mos-0865). The business file names claude only.
## 4. Upgrade, rollback and backup on .53
All three are `scripts/stack-upgrade.sh` (row 233), run as root by ops-01 from `/root/mosaic-stack` at the
reviewed commit. Usage, exits and the step log are in `docs/TOOLS.md`, "Root's upgrade driver", and run
sheet section 6.
| Action | Command | Who | Go |
|---|---|---|---|
| upgrade | `stack-upgrade.sh upgrade --instance dev --bundle /srv/mosaic/incoming/<sha>.bundle --sha <sha>` after run sheet section 6's per-upgrade lines | ops-01 | Mos per run; `--migrate` also needs Jason present (decision 96) |
| rollback | `stack-upgrade.sh rollback --instance dev --to <copy> [--restore <ACT> [--events N]]` | ops-01 | Mos per run. Mos or Sage picks `<copy>` from `stack-release.sh status mosaic-stack --units external`, read as `mosaic`. The plan refuses unless it is the previous distinct copy |
| backup of the store | A3's `backup_store` into `<dataRoot>/backups/stack/<ACT>`, inside every upgrade | the driver | the upgrade's |
| rehearsal | `stack-release.sh rehearse` against the log's newest backup, before anything stops, on `--rehearse-port`, default 7339, which section 0 found free | the driver | the upgrade's |
| backup of the VM | a PBS backup of VM 1008 before the install window and before any `--migrate` | ops-01 | the window's go (Q182, Q183: agents keep PBS) |
Three .53 specifics:
1. The first upgrade after install needs `--no-rehearsal` in its go. The install's adopt takes no backup,
so the log records none, the rehearsal exits 3 (`no-backup`), and the driver goes on only with the
flag (`stack-upgrade.sh:323-324`). Every later upgrade refuses the flag (`rehearsal-required`).
2. Exit 5 (`args-failed`): the new stack is live and confirmed, but `dev.args` still names the old copy.
ops-01 moves the file the result line names into place with `mv -f`, and nothing else (run sheet
section 6).
3. Exit 2 leaves both units stopped and the activation open. ops-01 sends Mos the exit and the last
lines of `/var/log/mosaic-stack/upgrade-dev.log`, and Jason or Mos settles it with `confirm` or `revert`
as on dragon-lin. A unit that doesn't stop within `--timeout` ends here on purpose (A2f's
`stale-start`, duckworth-0055 N1, now in run sheet section 6).
The store backup doesn't hold the config dir or the credential. PBS backups of VM 1008 hold the whole disk,
the credential included (P4). A restore swaps only the store and leaves `claude-session.env` in place.
Re-install, for a serve flag or role change, is not an upgrade. It needs its own go and runs: stop the
Console, then the bus; rewrite `dev.args` with the same `--copy`; `case2-unit.sh` with it; both units start
again from the render. Row 230 N1 (webby-0046) holds here: a re-render with the units running leaves each on
its old copy, so ops-01 stops both first. `stack-upgrade.sh` already stops both before it renders.
## 5. Acceptance: `scripts/case1-accept.sh`
New in this row. ops-01 runs it as root on .53, from `/root/mosaic-stack`:
```
case1-accept.sh --business mosaic-stack --args /etc/mosaic-stack/dev.args --claude-pin <pin> --node-pin <v>
```
It reads the user, home, node, claude and ports from the args file and refuses (exit 2) when that file
fails `stack-upgrade.sh`'s rules. Every check inside the instance runs as
`sudo -u mosaic -H env PATH=<node dir>:/usr/local/bin:/usr/bin:/bin`. It changes nothing on the host
except what F3's message writes to the store. It prints one line per check, `<id> <path> <mark>
<evidence>`, then a count of each mark. Marks: `closed`, `accepted Q182`, `accepted Q183`, `accepted
mos-1096`, `accepted mos-1101`, and `unnamed`, which means "not named in Q182, Q183 or a Mos ruling".
It exits 1 when any F check fails and 0 otherwise. A P check never changes the exit. Each `unnamed`
line goes to Mos, who takes it to Jason (R8 N1). Duckworth reads the whole output.
It prints no value from `claude-session.env` and never opens it.
Functional checks. Any failure fails the row:
| Id | Check | How |
|---|---|---|
| F1 | the units run as `mosaic` | for `[email protected]` and `mosaic-webui.service`: active, `User=mosaic`, and the uid in `/proc/<MainPID>/status` is `mosaic`'s |
| F2 | the Console answers on 127.0.0.1:732 only | `curl -fsS http://127.0.0.1:732/healthz` succeeds, and `ss -Hltnp` shows 732 bound to 127.0.0.1 only, by the Console's MainPID. Jason reaches it with `ssh -L 7332:127.0.0.1:732 [email protected]`. A LAN listener waits for Q175 SSO (sage-1451 Q3) |
| F3 | one tester message gets a reply | as `mosaic`: `mosaic talk chief <marker> --business mosaic-stack --tester --wait 300`. A stored reply to the marked message passes. The inbox shows decisions only, so it isn't used |
| F4 | the precheck passes | `case2-precheck.sh --business mosaic-stack --profile factory` as `mosaic` exits 0 |
| F5 | a role session runs under bwrap with the profile | while F3's reply is pending: a `/usr/bin/bwrap` process owned by `mosaic` has a claude child. Its `/proc/<pid>/attr/current` equals `bwrap (enforce)` and the child's equals `bwrap//&unpriv_bwrap (enforce)`, exact strings after the trailing newline is cut |
| F6 | claude is the pin | the args file's `--claude` path sits under `/opt/mosaic-stack/claude-<pin>/`, and its `--version` as `mosaic` prints `<pin> (Claude Code)` |
| F7 | the PM run is live | `launches list --json` as `mosaic` has exactly one live entry for the PM's instance (`launch.by`), the check `confirm` runs |
| F8 | the credential's file | `stat -c '%U %a %F'` on `claude-session.env` reads `mosaic 600 regular file`, and it isn't a symlink. Run sheet section 9 marks this "precheck line missing" |
| F9 | the autoupdater is off | `/etc/claude-code/managed-settings.json` is `root:root` 0644 and its `env.DISABLE_AUTOUPDATER` is `"1"`. Run sheet section 3 marks this "precheck line missing" |
| F10 | node is the pin | the args file's `--node` is `/opt/mosaic-stack/node-<v>/bin/node` with `<v>` the `--node-pin`, and its `--version` prints `<v>`. Run sheet section 2 marks this "precheck line missing" |
| F11 | no Discord binding | `/srv/mosaic/.mosaic-dev/discord/` is absent or holds no `*.json` (section 2) |
Host paths. These report and never fail the row:
| Id | Path | Mark | Evidence |
|---|---|---|---|
| P1 | `mosaic` has sudo | closed only when `sudo -l -U mosaic` prints "is not allowed to run sudo"; any other output is unnamed | the `sudo -l` result |
| P2 | `mosaic` has supplementary groups or a key | closed when `id -G mosaic` is one entry and `~mosaic/.ssh/authorized_keys` is absent or empty | `id`, file state |
| P3 | `localadmin` NOPASSWD sudo, reached with the dragon-lin key | accepted Q183 | `sudo -l -U localadmin` |
| P4 | root on pve over VM 1008, and PBS backups that hold `/srv/mosaic` with the credential | accepted Q182 and Q183 | a fixed line; the script can't see pve |
| P5 | `jarvis`, which runs Hermes, reaches `/srv/mosaic` through sudo, docker or lxd | closed when `jarvis` is in none of the three; unnamed otherwise. #423 step 4 removes all four groups before the install (mos-1091 4c) | `id jarvis` |
| P6 | other members of sudo, docker, lxd | closed when sudo holds only `localadmin` and docker and lxd are empty; each other member is its own unnamed line | `getent group` lines |
| P7 | a file under `/etc/sudoers.d` naming `mosaic` | closed when none | file names |
| P8 | the Console or tester reachable from the LAN | closed when 732 and 7333 are bound to 127.0.0.1 only, each by the Console's MainPID, and nothing listens on 7331 | listener table |
| P9 | a user namespace through `/usr/bin/bwrap` | accepted mos-1096 only while all eight hold, unnamed otherwise: hostcheck items 1 to 6, read from `/usr/local/lib/mosaic-stack/hostcheck.sh --check`'s own `ok N` lines (item 7 is P10's floor); `sudo -u jarvis /usr/bin/bwrap --version` fails with "Permission denied"; `getent group mosaic` lists no member and no other account has its gid. F5 adds the label of a real role session | the hostcheck lines and each item |
| P10 | a session binds the tester port 7333 while the Console is down | accepted mos-1101 while the port floor is above 732 and 7333 is held by the Console's MainPID | the floor, the owner pid |
| P11 | the boot check runs before the bus | closed when `mosaic-hostcheck.service` is enabled and `systemctl show -p Before` on it names `mosaic-bus@<business>.service` for the args file's business; unnamed otherwise (section 7) | the `Before=` value |
P9 reads hostcheck's output, which root's own script prints, by matching `^ok [1-6] ` lines. It runs `--check` only,
never `--enforce`, so the acceptance can't close bwrap.
Tests, in `packages/cli/tests/case1-accept.test.mjs`. The script takes a test seam, `--root DIR`, for
`/proc`, `/etc`, `/srv/mosaic` and `getent`, and refuses it when run as root, as the hostcheck does. Fake
`systemctl`, `curl`, `ss`, `sudo`, `bwrap`, `sysctl`, `stat`, `claude`, `node` and `hostcheck.sh` on PATH.
Nothing reaches .53 or dragon-lin's live state, and no test reads a real credential. Cases:
- every check passing exits 0, with the counts matching the lines;
- each F check failing alone exits 1 with its line;
- F5 fails on `unpriv_bwrap (enforce)`, `bwrap//&unpriv_bwrap (complain)` and a trailing space;
- F6 fails on a stub `claude.exe` that prints nothing and on a path outside `claude-<pin>`;
- F8 fails on mode 0640, on another owner and on a symlink, and the stand-in credential's value never
appears in the output;
- F11 fails on one `*.json` under `discord/`;
- each P check in each of its marks: P1 with output that grants nothing but lacks the refusal text
(unnamed), P9 unnamed once for each item missing, P10 unnamed with the floor at 700 and with 7333 held
by another pid, P11 unnamed with `Before=` naming another business;
- every P check `unnamed` still exits 0;
- a bad args file refuses with exit 2 before any check;
- every in-instance call goes through `sudo -u mosaic -H env PATH=<node dir>:...`, read from the fake
`sudo`'s recorded argv;
- the hostcheck fake records `--check` and never `--enforce`.
Mutants, each killed by a named case:
1. F1 passes on a MainPID owned by root.
2. F2 accepts a listener on 0.0.0.0.
3. F3 passes without a reply.
4. F5 matches a substring.
5. F6 skips the `--version` check.
6. F8 skips the owner check.
7. F11 ignores `discord/`.
8. P1 closes on any output.
9. P5 maps to `closed` with `jarvis` in docker.
10. P9 drops the jarvis item.
11. P11 accepts any `Before=`.
12. A P check's `unnamed` sets the exit to 1.
## 6. The .53 window
Nothing in this row touches .53 (sage-1809). Every root step below is ops-01's, over
`ssh [email protected]` with `sudo`, and waits on a Jason decision (mos-1086). The window needs:
- row 226 landed;
- Jason's or Mos's go naming the commit, the bundle's sha256, the node and claude pins, the role set and
Jason's human id;
- a backup first;
- a STATE report after.
| # | Step | Runs as | Source |
|---|---|---|---|
| W0 | re-read section 0's items; stop on any change and report it | `localadmin`, read only | section 0 |
| W1 | PBS backup of VM 1008 | ops-01 on pve | section 4 |
| W2 | the stack's user and home | root | run sheet 1 |
| W3 | node under `/opt` | root | run sheet 2 |
| W4 | claude under `/opt`, managed settings | root | run sheet 3 |
| W5 | bubblewrap, the profile, the statoverride, the no-new-privs test, the drift stop; `hostcheck.sh --check` exits 0 with item 7 at 1023 | root | run sheet 4 |
| W6 | the port floor (read) | root | run sheet 5 |
| W7 | the bundle, root's checkout at `<sha>`, `mosaic`'s clone | root, then `sudo -u mosaic -H` | run sheet 6 |
| W8 | `case2-install.sh --profile factory` with section 1's inputs: bootstrap, the business file, roles, human id, the copy | `sudo -u mosaic -H env PATH=...` | row 226 |
| W9 | place `claude-session.env` | Jason, or who he names | run sheet 9, section 3 |
| W10 | write `dev.args` (section 1); `case2-unit.sh` from it, which enables both units and starts the bus, then the Console | root | run sheet 7, 10 |
| W11 | `case2-precheck.sh --business mosaic-stack --profile factory` exits 0; `hostcheck.sh --check` exits 0 with item 7 at 732 | `mosaic`; root | row 230, 240 |
| W12 | `stack-release.sh adopt mosaic-stack --units external`, unless row 226's install does it | `sudo -u mosaic -H` | row 232 |
| W13 | `case1-accept.sh` (section 5); the output to Duckworth, then Mos confirms | root | this row |
| W14 | STATE report to Mos | ops-01 | brief |
Install rollback, any step before W13 passes. Stop and disable both units, remove their files and
`dev.args`, then the run sheet's rollbacks for sections 4, 3 and 2 in that order. Removing the user and
home (`userdel -r mosaic`) needs its own Mos go, since it deletes the home. W1's backup covers the rest.
What row 195 needs from row 226. Row 226 is assigned to Gosalyn, briefed and not started, so I'm listing what the window
needs from its design, not designing it:
1. `--profile factory` writes the business file with `bus.tester` on and no `library` key, and the roles
the go names.
2. It writes no token slot and no persona files (its brief says so).
3. It runs before the unit render, since the copy must exist for `case2-unit.sh --copy`, and it starts no
unit.
4. Who writes `dev.args`. The C2-13b brief says the install doesn't write unit files, and the run sheet
says "I2's install writes it". `mosaic` can't write `/etc/mosaic-stack`. So either root's part of I2
writes it, or ops-01 writes it from section 1 at W10. Either works for row 195. The file's content is
fixed above.
5. Whether it runs `adopt`. If it doesn't, W12 does.
I sent these to Gosalyn on 2026-10-21 (gosalyn-0085 acknowledged), and the run sheet takes whichever answer 226 gives.
## 7. The business id
.53's business id is `mosaic-stack` (sage-1813). It appears in:
- the args file, twice;
- the bus unit `[email protected]`;
- the business file name;
- `case1-accept.sh --business`.
The landed boot unit `packages/cli/systemd/mosaic-hostcheck.service` has
`[email protected]`. So at boot the drift stop runs before .53's bus, and DEFERRED 240
N1 (da3051ccc: `After=mosaic-hostcheck.service` in `mosaic-bus.service.in`) doesn't bind this row.
If a go ever names another id for .53, the window stops before W8, and the N1 fix lands first as its own
reviewed row. P11 is the check on the host. It reads `unnamed` when the boot unit's `Before=` doesn't
name the installed bus.
DEFERRED 240 N2 (the apt hook goes in before the first `--check`) is a run sheet order note. This row
doesn't edit the run sheet, so it stays deferred.
## 8. Rulings this design rests on
- sage-1451: Q1 system units; Q3 the Console on 127.0.0.1 with `ssh -L`; Q4 a rebase goes to a new branch
name (#426(a), mos-1086).
- mos-1091, mos-1096, mos-1098, mos-1101: the bwrap path, its profile and P9; the port squat and P10; .53
runs the PM. Their r6 sections stand, and rows 232, 233, 240 and 230 implemented them.
- mos-1108 (Q187) and mos-1118: the installer rows and the scope cut. mos-1122 (Q189): both units start at
boot.
- sage-1522: claude under `/opt`, root-owned.
- sage-1809: no step on .53, root steps named and left to ops-01, the business id stated.
- sage-1813: 226 in the after list (rev 2718); the id is `mosaic-stack` and 240 N1 doesn't apply.
- Q185: the factory's model account. Q173: credentials placed by Jason or his named person.
## 9. Manifest (planned)
- `scripts/case1-accept.sh` (new), mode 100755.
- `packages/cli/tests/case1-accept.test.mjs` (new). Section 5's cases and mutants.
- `docs/TOOLS.md`: a `case1-accept.sh` entry with usage, marks and exits, beside the hostcheck entry.
- `agents/darkwing/work/queue-195/BUILD.md` (new): run environment, the mutant results, deviations.
- No change to `case2-*.sh`, `stack-upgrade.sh`, `stack-release.sh`, `case1-hostcheck.sh` or the run
sheet. Two follow-ups stay out of this row:
- row 230 r2 N1. The "still on copy" line reads the id from the unit file. On .53 neither the install
nor `stack-upgrade.sh` reaches that line, since both start from stopped units.
- DEFERRED 240 N2.
- No change to `~/.mosaic-dev`, `~/.config/mosaic-dev`, dragon-lin's units or `~/.mosaic`.
Gate: Duckworth approves this design, then the manifest; the cli suite and every `scripts/test-*.sh`
green on Sage's gate; section 5's twelve mutants killed in a slot Sage grants.
+31
View File
@@ -254,3 +254,34 @@ checked. The script stops on any restore mismatch, and it ran to its end.
17 of 17 killed, 01:22:16 to 01:24:32Z. I read the logs for m01 and m09, two of the quick kills: each failed on
its named test's assertion (m09 on `AWAITING-UNITS` in sequence 2), not on a load error.
## Round 2: rebase onto 8fede5fc6 (sage-1673)
Rebase only. Before the landing gate, a check of BASE..HEAD on the manifest paths found that row 230
(c8bc5cf4c) had changed one of them after the round 1 base, cb5fdb064. Sage ruled a re-round with a
scoped review, the same shape as row 210 round 3.
Branch `darkwing/row232-r2`: the two round 1 commits, a0ffabc3f and 84f1a91e6, cherry-picked onto
8fede5fc6 with no conflict, then this section. No code or test changed.
- `git merge-tree --write-tree origin/refactor darkwing/row232-r1` (refactor at 8fede5fc6) gives tree
837bde79236c with no conflict. The cherry-picked tip before this section has the same tree.
- `packages/cli/README.md` is a clean 3-way merge. Row 230's hunk is at lines 710-717 (the session credential
paragraph); this row's is a new paragraph at 773. With `git patch-id --stable`, the README diff from r1 to
r2 is 02b679e2, the same as row 230's README diff `cb5fdb064..8fede5fc6`. This row's README diff is
c395ecd2 on both bases (`cb5fdb064..r1` and `8fede5fc6..r2`).
- The seven code, test and doc paths other than the README hash the same as in manifest 7a3b4ea1, which
Filbert approved in round 1: `docs/TOOLS.md`, `packages/cli/tests/stack-copy.test.mjs`,
`packages/cli/tests/stack-release-external.test.mjs`, `packages/cli/tests/stack-release-fixture.mjs`,
`packages/cli/tests/stack-release.test.mjs`, `scripts/stack-copy.sh`, `scripts/stack-release.sh`.
This file also matched until this section was appended. Its other lines are unchanged.
- The one test that reads the README, `host.test.mjs:855`, looks for the unit's `EnvironmentFile=` line.
That line is unchanged context in row 230's hunk.
- No row 230 test reads `stack-release.sh` or `stack-copy.sh`, and no test of this row reads a row 230 file.
Nothing was run for this round. The landing gate runs on the r2 tip on Sage's slot.
Follow-up, doc-only (sage-1673): `scripts/case2-unit.sh:108` and `scripts/case2-precheck.sh:157` cite
`scripts/stack-copy.sh:8` and `:22` for `COPIES` and `copy_root`. With this row those lines are `:10` and
`:26`. Only comments are affected and no test reads them. Row 233 doesn't touch those scripts, so the fix
goes on the #1732 carry list.
+249
View File
@@ -0,0 +1,249 @@
# Row 233 (I3): root's upgrade driver and the host run sheet
Brief: `docs/plans/2026-10-20_installer-rows.md` section I3, blob `e61291bcf1c6` (commit a0e3d7cfd), re-pinned
by Sage (sage-1685, rev 2631) from `f35e7e57daee` (ad6f0be7a). The later blob only appends I4; the I3 text
is the same. Gitea #1735, Vikunja P19
#432. Owner Darkwing, reviewer Filbert, gate owner Sage. Design input: row 195 DESIGN r6,
`agents/darkwing/work/queue-195/DESIGN.md` at b7239bb66, sections 2, 4 and 7. Where the brief and r6
differ, the brief wins. Sage's rulings during the build: sage-1650 and sage-1652 (`--backup last`, the
`--no-rehearsal` mapping, `--rehearse-port`, hostcheck), and sage-1653 (`from_log newest`, the two external
plan refusals, the `BACKUP_STORE` line).
## Files
Base: e65a3ab4d, row 232's landing, on `darkwing/row233-r1`. The build started on ea1e85c7b
(`darkwing/row233`, row 232's candidate e6ca230d2 and its BUILD.md) before row 232 landed. The three row 232
files this row changes are identical on the two bases, so the uncommitted diff moved across unchanged.
- `scripts/stack-upgrade.sh` (new): `upgrade` and `rollback`, root's side of r6 A1-A5, A2f and R1-R4.
- `packages/cli/tests/stack-upgrade.test.mjs` (new, 20 tests).
- `docs/guides/host-run-sheet.md` (new).
- `docs/TOOLS.md`: a new subsection "Root's upgrade driver (`scripts/stack-upgrade.sh`, row 233)", plus the
row 232 lines below.
- Row 232 files, changed on Sage's rulings (the brief's files list doesn't name them):
- `scripts/stack-release.sh`:
- `rehearse --backup last` takes the newest backup the log records (`from_log newest`) and prints
`PASS backup-last`. With none it fails `no-backup`, exits 3 and makes no rehearsal directory.
A newest backup that isn't an activation id aborts `backup-unreadable`, exit 1.
- The rehearsal prints `BACKUP_STORE <activation id> schema <version>` before its migration step.
- `--plan` under `--units external` refuses `open-activation` when any rollback set holds
`AWAITING-UNITS`, and `migrate-required` for a migrating target without `--migrate`. Root reads
only the plan's exit, so these two have to be refusals there. User mode prints the same bytes.
- `packages/cli/tests/stack-release.test.mjs`: three new `row 233:` tests for `--backup last`.
- `docs/TOOLS.md`: `--backup ACTIVATION|PATH|last` in the rehearse usage, a paragraph on `last`, and a
paragraph on the two external plan refusals.
- This file.
## The brief, item by item
| Brief item | Where |
|---|---|
| Root runs from a root-owned checkout, never a copy | `head-mismatch` (A1); TOOLS.md; run sheet section 6 |
| I2's render with `/etc/mosaic-stack/<instance>.args`, `--copy` the one changed value | `read_args`, `args_with`; the args test |
| The args file changes only after a confirmed run | `prepare_args` before A2, `mv` after A5; sequences 1 and 7, m6 |
| `copy --bundle` as the stack's user before A1 | the `fetch`, `ff`, `copy` steps; sequence 1's sudo keys |
| A rehearsal before A2, a failed one refuses with nothing stopped | `rehearse`; three rehearsal tests, m4 |
| The root lock, held to the end, rollback included | `flock -n 9`; the lock test and sequence 3's probe, m1, m2 |
| A1 refuses `open-activation` | stack-release.sh's external plan; the AWAITING-UNITS test, m3 |
| `migrate-required` without `--migrate` | stack-release.sh's external plan; its test, m5 |
| The step log, 0640, one line per step | `logl`, `step`; every test checks lines against `STEP` |
| Exit codes 0-3 | `finish`, `refuse`; exit 4 is usage |
| duckworth-0055's notes | the next section |
| Root parses no stack user output beyond exits | the step log test; the one exception is sage-1653's `BACKUP_STORE` |
| The host run sheet | `docs/guides/host-run-sheet.md` |
## duckworth-0055's notes on r6 (brief line 169)
- N1, A2's stop timeout under `gate --since`. Row 232's BUILD.md left two pieces to I3. The run sheet's
A2f line is in section 6 ("When a run doesn't exit 0", exit 2). The test is `N1: a bus that outlives
A2's timeout keeps its old start`: a bus that ignores `stop` keeps its old `ActiveEnterTimestamp`,
`wait-down` logs `rc=1 ... state: active`, A2f restarts and gates, the gate fails `stale-start`, and the
driver stops both units and exits 2. Sequence 4 is the other half: an A3 exit 3 goes to A2f and exits
1 with the old copy's gate passing.
- N2, the claude path under `/opt/mosaic-stack`. Run sheet section 3 installs it there as root with
managed settings. The args file's `--claude` carries it, and `case2-unit.sh` already refuses a binary
that isn't root's or sits under the home. The driver passes the args file through and adds no claude
check of its own. F6 is I2's.
## Sage's rulings during the build
- sage-1650 and sage-1652. Root always runs the rehearsal and maps its exit:
- 0 goes on, or refuses `rehearsal-required` with `--no-rehearsal`.
- 3 refuses `no-backup`, or with `--no-rehearsal` logs `rehearsal-skipped` and goes on.
- Anything else refuses `rehearsal-failed`.
`--rehearse-port N`, default 7339. Hostcheck: a missing file or a nonzero `--check` refuses
`profile-drift`.
- sage-1653. `from_log newest`, and the external plan refusals `open-activation` and `migrate-required`.
Residual: the `BACKUP_STORE` id and schema land in root's step log, but nothing ties that backup to the
one the activation's own backup step takes later. A stopped backup before the upgrade would close that
gap; it's a later row, not this one.
## Tests
`stack-upgrade.test.mjs` runs the driver as the test's user against a scratch root, with the test seams
`STACK_UPGRADE_*`. The stand-ins are a fake `sudo` (logs its argv, checks the driver's form, then runs the
real command as the test's user, r6's B1 pattern), a fake `case2-unit.sh` render that installs and starts
the fixture units, a fake `systemctl` for root's system units, and a shell `hostcheck.sh`. The stack's
user's steps run the real `stack-release.sh` and `stack-copy.sh` from a clone made from a real bundle. The
units, copies and store are `stack-release-fixture.mjs`'s.
| Test | Covers |
|---|---|
| sequence 1 | confirm fails healthz, rollback with `--cause A5`, exit 1; every sudo call in the driver's form |
| sequence 2 | `activate --migrate` fails at the wrapper, store restored, `--cause A3`, exit 1 |
| sequence 3 | render fails, `--cause A4`; a probe run during `render-old` refuses `upgrade-running` |
| sequence 4 | stage exits 3, A2f restarts and gates the old units, exit 1 |
| N1 | a unit that won't stop: A2f's gate fails `stale-start`, exit 2, units stopped |
| sequence 5 | confirm of the wrong copy refused, `--cause A5`, exit 1 |
| sequence 6 | revert fails, exit 2; the next run refuses `interrupted-run` at its plan |
| sequence 7 | two upgrades and a `rollback --to` with `--restore`; the args file after each confirm; `to-not-previous` |
| sequence 8 | a migration that writes and fails, store back byte for byte, exit 1 |
| lock | a second run while the lock is held: one log line, no call |
| open-activation | any `AWAITING-UNITS` refuses at A1, nothing stopped |
| migrate-required | refused without `--migrate`, no rehearsal, nothing stopped |
| rehearsal-failed | the rehearsal's argv, exit 3, nothing stopped |
| `BACKUP_STORE` | the line after a match; four malformed lines log `BACKUP_STORE none` |
| no-backup | the real rehearsal with no backup refuses; `--no-rehearsal` logs the skip and goes on |
| backup-last | the real rehearsal names the newest backup; `--no-rehearsal` with one refuses `rehearsal-required` |
| args file | group write, other write, two `--copy`, an unknown flag, a symlink, `args-differ` |
| hostcheck | nonzero `--check` and a missing file refuse `profile-drift` before the plan |
| head-mismatch | checkout not at `--sha`; a commit the clone can't fast-forward to refuses `fetch-failed` |
| usage | twelve bad argvs exit 4 with no log and no lock |
The step log check (`STEP`) runs in every test that reads the log: each line must be a step line or the
result line, and none may hold the stack user's output (`stack-release:`, `PASS `, `FAIL `, `REHEARSAL`,
the fake rehearsal's text).
## Mutants
`~/darkwing-scratch/r233/mutants.sh`, not committed because it edits the worktree. It has the brief's six
mutants and two for sage-1650's mapping. The method is row 232's: a checked backup, exact replacements, a
syntax check, the named killers, then the file put back with its sha256 checked. One control runs first:
the whole `stack-upgrade.test.mjs` with no change. `DRY=1` matched each replacement once on the candidate
(2026-10-21).
| Mutant | File | Killers |
|---|---|---|
| m1 drop the flock | stack-upgrade.sh | `a second run while the lock is held`, `sequence 3:` |
| m2 release the lock when the rollback starts | stack-upgrade.sh | `sequence 3:` |
| m3 skip A1's open-activation check | stack-release.sh | `A1 refuses open-activation` |
| m4 skip the rehearsal | stack-upgrade.sh | `the rehearsal runs after the plan`, `sequence 1:` |
| m5 accept a migrating target without `--migrate` | stack-release.sh | `migrate-required` |
| m6 write the args file before confirm | stack-upgrade.sh | `sequence 7:`, `sequence 1:` |
| m7 `--no-rehearsal` accepted with a backup | stack-upgrade.sh | `refuses rehearsal-required` |
| m8 drop the `rehearsal-skipped` line | stack-upgrade.sh | `a real rehearsal with no backup` |
## Deviations
D1. The brief has A1 refuse `open-activation` for an open `AWAITING-UNITS` or a non-empty `interrupted`
set. The interrupted case refuses with row 232's existing code, `interrupted-run`; only `AWAITING-UNITS`
gives `open-activation`. Both reach root as `plan-refused`, exit 3 (sequence 6, the open-activation test).
D2. Root copies one line of the stack user's output into its log, `BACKUP_STORE`, on sage-1653. It
matches a fixed pattern first, takes the last match, logs `BACKUP_STORE none` otherwise, and decides
nothing on it.
D3. The stack user's steps carry no `--instance`. The driver takes `--instance dev` only, row 190's default
instance, which the stack user's commands select with no flag. A named instance's units are a later row.
D4. Most tests fake the rehearsal in the fake `sudo`: the fixture's stub copies can't pass a real one. Two
tests run the real rehearsal (`faults/real-rehearse`): no-backup and backup-last. `stack-release.test.mjs`
covers `--backup last` itself.
D5. Any nonzero `--plan` exit refuses `plan-refused`, exit 3, whatever the code. The stack user's output
above it names the refusal; root doesn't parse it.
D6. `rollback --restore ACT` is accepted without `--events`, as `stack-release.sh rollback` accepts it.
The brief's usage pairs them. `--events` without `--restore` is a usage error.
D7. The refusal of any `STACK_UPGRADE_*` seam when run as root has no test, because the tests don't run
as root. It is three lines at the top of the script.
D8. The fake `sudo` keeps the test's environment, where real `sudo` resets it. The driver passes
everything the stack user needs through `env PATH=...` and `-H`, and the fake checks that form.
D9. Exit 3 means nothing stopped, not nothing changed. A refusal after `fetch` leaves the stack user's
clone fast-forwarded and the new copy made. Both are the user's own state, and the next run reuses them.
TOOLS.md and the run sheet say so.
D10. The brief's usage has neither `--no-rehearsal` nor `--rehearse-port`. Both come from sage-1650 and
sage-1652.
## Decisions
- `rollback` has no HEAD check. Its target is an older copy than root's checkout, which already exists.
It fetches, copies and rehearses nothing (sequence 7).
- The new args file is written next to the old one (`.dev.args.XXXXXX`, same mode) before A2, so a full
`/etc` refuses before anything stops. It moves into place after A5. A2f and the rollback remove it; a failed move keeps it and names it.
- Every stack user step gets `--copies` explicitly, so the copies directory comes from the args file's
`--home`, never from the user's environment.
- The stack's user does the fetch, the fast-forward and the copy, so root never runs git on a tree that
user can write.
- A4 is the render, and `case2-unit.sh` starts the bus, then the Console. The forward path has no
`systemctl start` of its own; only A2f does.
## Run environment
Worktree `/mnt/storage/scratch/darkwing-r233`, `TMPDIR=/mnt/storage/scratch/tmp/r233`, node from PATH. One
whole `stack-upgrade.test.mjs` run takes about 56 s.
## Results
### Singles, run 1, 2026-10-21, Sage's slot (sage-1689)
`~/darkwing-scratch/r233/singles.sh` on `darkwing/row233-r1` (base e65a3ab4d, uncommitted tree), 03:48:20Z
to 03:59:00Z. These are the test files that read a changed script. `stack-instances.test.mjs` reads
`stack-release.sh`, so it joined the three named in the plan.
| File | Result |
|---|---|
| `stack-upgrade.test.mjs` | 20 pass, 0 fail |
| `stack-release.test.mjs` | 194 pass, 1 fail |
| `stack-release-external.test.mjs` | 25 pass, 0 fail |
| `stack-instances.test.mjs` | 23 pass, 0 fail |
The failure was my test, not the script. "row 233: --backup last takes the newest backup..." compares
`untouched(e)` before and after the run, and `untouched` includes `existsSync(e.log)`. The test took its
before snapshot ahead of `rlog()`, which writes the log, so that flag went from false to true. Every hash in
the snapshot matched. The fix takes the snapshot after `rlog()`. No other row 233 test calls `untouched`.
### Singles, run 2
`~/darkwing-scratch/r233/rerun.sh` on aacfa2d7a, Sage's slot (sage-1695), 04:13:05Z to 04:21:28Z:
`stack-release.test.mjs` whole, 195 pass, 0 fail, 0 skipped. The fixed test passed. The other three files
did not change after run 1. The gate and the mutants run next, in Sage's slot.
### Gate and mutants, 2026-10-21, Sage's slot (sage-1704)
`~/darkwing-scratch/r233/gate.sh` then `mutants.sh`, chained behind `busy.sh`, on d99166ade (code
aacfa2d7a; d99166ade changes only this file). Counts are an awk sum over `gate-r1.out`.
| Part | Time (UTC) | Result |
|---|---|---|
| cli suite (`node --test tests/*.test.mjs`) | 04:29:01-04:37:27 | 616 pass, 0 fail, 0 skipped |
| the 10 `scripts/test-*.sh` | 04:37:27-04:39:38 | selftests 429 passed, 0 failed; `test-queue`'s node part 156 pass, 0 fail |
| gate total | | 1201 pass, 0 fail, 11 suites, `GATE_PASSED` |
| c0 control | 04:39:38-04:40:35 | exit 0 |
| m1-m8 | 04:40:35-04:41:25 | 8 of 8 killed, every one exit 1 |
Every restore passed its sha256 check, and `git diff` was clean at d99166ade afterwards. The EXIT trap
removed the `darkwing-r233_default` network.
N1, found in the process sweep after the run: a `sleep 60` was left with its cwd in `packages/cli`.
`stack-upgrade.test.mjs:312` spawns `flock LOCK sleep 60`, and its `t.after` SIGKILLs only the flock, so
flock's child outlives the test by up to 60 s. It exited on its own. Row 232's T20
(`stack-release.test.mjs:370`) spawns its holder `detached` and kills the group with `process.kill(-pid)`.
The fix is the same two lines. That test is one of m1's killers.
Sage ruled it a fix now (sage-1707), and 08db6ac3f makes it, test only. After the gate's d99166ade
the only change outside this file is that test:
```
$ git diff --stat d99166ade 08db6ac3f
packages/cli/tests/stack-upgrade.test.mjs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
```
The gate stands on Sage's ruling. The rerun is the whole `stack-upgrade.test.mjs` singly plus m1, in
Sage's slot, with a sweep for a leftover `sleep` afterwards.
+173
View File
@@ -0,0 +1,173 @@
# Row 240 (I4): root's host check, its pins and the boot unit
Brief: `docs/plans/2026-10-20_installer-rows.md` section I4, blob `4a5a65020f00` (commit b00d7d94d, rev 2665,
the pin row 240 carries; the I3 carry items came in with it). Sage split it out of I3 (sage-1650). Gitea #1746, Vikunja P19 #432. Owner Darkwing, reviewer Filbert, gate owner Sage.
Design: row 195 DESIGN r6 B2, `agents/darkwing/work/queue-195/DESIGN.md` at b7239bb66, :262-303 (the script,
the three callers) and :839-860 (files, test cases, mutants). Where the brief and r6 differ, the brief wins.
## Files
Drafted on `darkwing/row240-r1` from d99166ade (row 233's candidate) while row 233's gate ran, then applied
as one patch onto row 233's landing, 57f59e0c3, on `darkwing/row240-r2` (sage-1753). It applied cleanly; between
the two bases the only change on this row's paths is 233's N1 test fix in `stack-upgrade.test.mjs`. Row 234
then landed as c771a36f4 on 57f59e0c3. Sage preferred that the gate run with it in the tree (sage-1773), so both
commits were picked onto c771a36f4 as `darkwing/row240-r3`. That was a clean pick: the 42 paths changed from
57f59e0c3 to c771a36f4 share none with this row's 9.
- `scripts/case1-pins.sh` (new, 0644): `PIN_PROFILES_VERSION`, `PIN_PROFILE_SHA256`, `PIN_BUBBLEWRAP_VERSION`,
r6 section 0's values.
- `scripts/case1-hostcheck.sh` (new, 0755): the seven items and `--check`, `--enforce`, `--upstream`.
- `packages/cli/systemd/mosaic-hostcheck.service` (new): the boot oneshot.
- `packages/cli/tests/case1-hostcheck.test.mjs` (new, 16 tests). The cli suite's `tests/*.test.mjs` glob picks
it up.
- `docs/guides/host-run-sheet.md` section 4: the install lines that replace "Install them from row 240's run
sheet lines once it lands", plus rollback, drift recovery and `--upstream` before a re-pin window. Sections
3, 6, 8 and 10 for the I3 carry items below.
- `scripts/stack-upgrade.sh` and `packages/cli/tests/stack-upgrade.test.mjs`: I3 N1 below.
- `docs/TOOLS.md`: a new subsection "Root's host check (`scripts/case1-hostcheck.sh`, row 240)" after the
upgrade driver's.
Brief line 257 said I3's `stack-upgrade.sh` doesn't change. Sage's ruling sage-1727 widened the row to it for
N1 alone. Its default `STACK_UPGRADE_HOSTCHECK=/usr/local/lib/mosaic-stack/hostcheck.sh` is where the run
sheet installs this script.
## Carried from I3
Filbert's row 233 approve (Gitea 29922) left five notes. Sage ruled N1, N4 and N5 into this row and N2 and N3
to DEFERRED.md (sage-1727).
| Note | Change |
|---|---|
| N1: `stack-upgrade.sh` exits 1 `args-failed` when the args move after A5 fails, but exit 1 means the old copy runs | The move's failure exits 5 `args-failed`. The header, TOOLS.md's step 5 and exit line, and run sheet section 6's exit list name exit 5 and what root does: move the new file the result line names into place. Test `row 240: I3 N1: a failed args move after A5 exits 5 ...` makes `/etc/mosaic-stack` read-only at confirm and checks exit 5, the step names, `args rc=1`, the result line, both units on the new copy, the old args file's bytes and the new file beside it. The step log regex takes `exit=5`. Mutant M8. |
| N4: section 3's `npm install` has no npm on root's PATH, and the `--claude` path is unnamed while `node_modules/.bin/claude` is a symlink `case2-unit.sh:140` refuses | The install line runs with section 2's pinned node first on PATH (the postinstall runs `node` too). The `--claude` path is `node_modules/@anthropic-ai/claude-code/bin/claude.exe`, and a `--version` line catches the postinstall's 500-byte stub. |
| N5: sections 8 and 10 lack precheck lines | Each gets "Precheck line: none" and its missing line. |
Exit 5 is new rather than 2 because 2 means both units are stopped, and here both run. Exit 4 stays usage.
The `claude.exe` path comes from the 2.1.304 package itself: `npm view` gives `bin.claude` as `bin/claude.exe`,
and its `install.cjs` hardlinks the platform binary over that placeholder, or copies it across devices, then
sets 0755. I downloaded the tarball into `/mnt/storage/scratch/tmp/r240/cc304` and read it; nothing ran. The
run sheet says a newer pin needs the same check.
## The brief, item by item
| Brief line | Where |
|---|---|
| 247 `case1-pins.sh` holds the three pins | `scripts/case1-pins.sh`; the pins test checks the values against r6's |
| 248-249 seven items, `--check` exits 0 or 1 | `items()` and the final dispatch; the check tests |
| 250-251 `--enforce`: `apparmor_parser -R`, `root:root 0700`, `DRIFT`, a log line, exit 0 | `enforce()` and `stop_bwrap()`; the four enforce tests |
| 252 `--upstream` compares the current deb to the pin | `upstream()`; the upstream test |
| 253 the boot oneshot | `mosaic-hostcheck.service`; the boot unit test |
| 254-255 fakes for every host tool, r6's seven case groups, seven mutants | the test file; Tests and Mutants below |
| 257 the I3 run sheet item points here | run sheet section 4 |
## Tests
Fakes on PATH, each logging its argv to `$HC/calls.log`: `aa-status`, `dpkg`, `dpkg-statoverride`,
`apparmor_parser`, `systemd-run`, `stat`, `chown`, `chmod`, `apt-get`, `dpkg-deb`. `host(t)` builds a scratch
root (`/etc`, `/proc/sys`, `/usr/bin/bwrap`, `/var/log`) and a lib dir with a copy of the script and fixture
pins that hold the fixture profile's sha256. Every run passes `--root`.
r6's case groups (:843-856) against the tests:
| r6 case | Tests |
|---|---|
| `--check` exits 0 with all seven, 1 with each failing alone, its line naming it | `check: exits 0 ...`, `check: exits 1 with each item failing alone ...`, plus one test per item for the item's edge cases |
| item 3: a second file naming `bwrap` as a word, `/{usr/,}bin/bwrap` included; passes with the profile alone | `check: item 3 ...` |
| item 4: `unpriv_bwrap (enforce)` or a trailing space fails; the argv carries `-p NoNewPrivileges=yes` and the 2a' properties | `check: item 4 ...` checks the whole argv |
| item 7: the floor at 732 fails | `check: item 6 ...; item 7 ...` (733 passes as the control) |
| `--enforce` exits 0 on a pass and every failure; unload, close, `DRIFT`, one log line; `DRIFT` present with every item passing still closes | `enforce: a pass ...`, `enforce: each failure ...`, `enforce: with DRIFT present ...` |
| `--enforce` never calls `dpkg-statoverride` with `--update`, `--add` or `--remove` | `enforce: no unload ...; never writes the statoverride` (every call is `--list /usr/bin/bwrap`) |
| a bubblewrap version other than the pin is a line, not a failure | `check: a bubblewrap version other than the install pin ...` |
| `--upstream` against a differing deb prints the difference, exits 0 | `upstream: ...` (also: a match, a deb with no profile, a failed download) |
| the unit: `Type=oneshot`, `After=apparmor.service`, `[email protected]`, runs `--enforce` | `boot unit: ...` |
The last test also checks that the repo script reads `scripts/case1-pins.sh` beside it, that bad usage exits
2, and that a non-root run without `--root` refuses.
Singles are the two files that run a changed script: `case1-hostcheck.test.mjs` and `stack-upgrade.test.mjs`.
Sage asked (sage-1759) whether `stack-release.test.mjs` or `stack-release-external.test.mjs`, which share
`stack-release-fixture.mjs`, run `stack-upgrade.sh` or `case1-pins.sh`. They don't. `grep -cF` for
`stack-upgrade`, `case1-pins`, `case1-hostcheck` and `hostcheck` gives 0 in all three files; the control
`stack-release.sh` gives 2 in the fixture and 8 in `stack-release.test.mjs`. `scripts/stack-release.sh` names
`stack-upgrade.sh` once, in a comment (:2061). The gate's cli suite runs both files anyway.
## Mutants
r6's seven (:858-860) and one for I3 N1, scripted in `~/darkwing-scratch/r240/mutants.sh` with r233's method: a checked backup,
an exact replacement that must match once, `bash -n`, the named killer by `--test-name-pattern`, the restore
and a sha256 check. A dry run matched all eight once each. Results below.
| Mutant | Change | Killer |
|---|---|---|
| M1 | `--enforce` exits 1 after the stop | `enforce: each failure exits 0 ...` |
| M2 | `[ ! -e "$DRIFT" ] \|\| had=1` becomes `true \|\| had=1` | `enforce: with DRIFT present ...` |
| M3 | item 1's `dpkg -S` becomes `owner=""; rc=1` | `check: item 1 fails when a package owns ...` |
| M4 | item 3's `grep -rlw bwrap` becomes `grep -rlF /usr/bin/bwrap` | `check: item 3 ...` |
| M5 | item 4's `[ "$label" = "$LABEL" ]` becomes a substring match | `check: item 4 ...` |
| M6 | item 4 drops `-p NoNewPrivileges=yes` | `check: item 4 ...` (argv) |
| M7 | item 7's `-gt` becomes `-ge` | `check: item 6 ...; item 7 fails with the floor at 732 ...` |
| M8 | `stack-upgrade.sh`'s `finish 5 args-failed` becomes `finish 1 args-failed` (I3 N1) | `row 240: I3 N1: a failed args move ...` |
## Deviations
- Repo name `scripts/case1-hostcheck.sh` (r6), installed as `/usr/local/lib/mosaic-stack/hostcheck.sh`, the
name in brief line 239 and I3's `stack-upgrade.sh` default. r6's apt hook line says `case1-hostcheck.sh`;
the run sheet's hook uses the installed name.
- `stat` is a fake too. r6 lists seven fakes; a test can't make a root-owned file, so item 5's `stat` reads a
fixture.
- Item 7 with no Console unit installed reads the port as 1023 and says so ("1023, because no Console unit
is installed"). Until I2 installs the unit the floor must be above 1023. A unit with no `--port` fails.
- Item 4 runs under `timeout 60`. A hung `systemd-run` would otherwise hang the apt hook and the boot unit.
## Decisions
- Fail closed where r6 is silent:
- pins with no 64-hex sha fail item 1;
- `dpkg -S` exit 1 (no owner) passes, exit 0 fails with the owner, any other exit fails with the code;
- a failed `aa-status` fails item 2 and counts as loaded, so `--enforce` tries the unload.
- `--root DIR` is the test seam (r6 :840). Root refuses it; a non-root user needs it. Both exit 2. Root runs
with `PATH=/usr/sbin:/usr/bin:/sbin:/bin`.
- `DRIFT` is append-only (`<stamp> <failed line>`), and `--enforce` never removes it. The log line is written
only when the stop runs: `<stamp> enforce stop items=<ids or none> drift-before=<0|1> unload=<rc=N|no-file|skipped> chown=N chmod=N`.
- `--upstream` exits 1 when the download or the unpack fails, since it then compared nothing. A deb without
the profile file is a difference (exit 0), per r6 :302-303.
- The bubblewrap note reads the version from `dpkg -s bubblewrap`.
- The run sheet's recovery steps (`chown root:mosaic`, `chmod 0750`, `apparmor_parser -r`, then `--check`
before root removes `DRIFT`) are mine. r6 says only that root removes `DRIFT` in a reviewed step.
## Run environment
Worktree `/mnt/storage/scratch/darkwing-r240b`, branch `darkwing/row240-r3` on c771a36f4, with `node_modules`
linked to the canonical checkout's. Node v26.8.1, kernel 7.2.2-1-cachyos, 16 CPUs, TMPDIR
`/mnt/storage/scratch/tmp/r240`, compose project `darkwing-r240b`. Sage's slot (sage-1773, 06:16Z). Each run is
one chain behind `busy.sh`, launched from `/`: `~/darkwing-scratch/r240/run-all.sh` checks the HEAD pin and a
clean tree, runs the singles, then the mutants (8 killed, none surviving, the tree clean after), then the gate
(the cli suite and every `scripts/test-*.sh`), each stopping the chain on failure, then sweeps `/proc` cwd links
for anything left in the r240 trees.
## Results
Run r1, 06:17:10Z on 47ec9fe2f, failed at the singles. `case1-hostcheck.test.mjs` passed 16/0/0, and
`stack-upgrade.test.mjs` went 20 pass, 1 fail. The failure was the N1 test's own `t.after` hook, which ran
`chmodSync(e.etc, 0o755)` after the fixture's cleanup had already removed the scratch root, so it threw ENOENT.
Every assertion in the test body had passed, exit 5 included. The fixture's cleanup runs `chmod -R u+w` on the
root before `rmSync`, so the 0555 dir never needed the hook. b45852daa drops it and changes nothing else. The
mutants and the gate didn't run. The sweep found nothing left. Sage counted r1 as this slot's one failure
(sage-1788).
Run r2, 06:20:41Z to 06:33:28Z on b45852daa, passed. The counts come from awk over the .out files
(`~/darkwing-scratch/r233/sum.sh`):
| Step | Result |
|---|---|
| `case1-hostcheck.test.mjs` | 16/0/0 |
| `stack-upgrade.test.mjs` | 21/0/0 |
| c0 control | exit 0 |
| M1-M8 | 8/8 killed, none survived, every restore's sha256 matched, tree clean after |
| `packages/cli` suite | 639/0/0 |
| `scripts/test-*.sh` | auth 15, conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86, queue 156 node + 28 selftest, release 14, task 98; 585 pass, 0 fail |
| Gate total | 11 suites, 1224 pass, 0 fail |
| Sweep | nothing left in the r240 trees; the compose network was removed |
This commit adds only these BUILD.md lines. No test reads BUILD.md.
+91
View File
@@ -0,0 +1,91 @@
# Row 245 build: Duckworth's N1-N3 on `scripts/case1-accept.sh` (row 195 follow-up)
Duckworth approved row 195's manifest round 1 (duckworth-0088, `agents/duckworth/work/row195/REVIEW-m1.md`
at 6afcee2a5) with three notes that don't block. Row 195's code landed as b65014647, and the notes are in
`docs/plans/DEFERRED.md` (d6cff0cdd). Row 245 carries them: brief `docs/plans/2026-10-20_hub-own-uid.md` §
"Row 195 follow-up" at 62ddc96cc, owner Darkwing, reviewer Duckworth, gate owner Sage (sage-1923).
The branch is `darkwing/row245`, on 62ddc96cc. Its first commit is a cherry-pick of `darkwing/row195-n1`
1e3c4767c, which was written on 37171f65b and never run. The script, test and TOOLS.md blobs are the same
as in 1e3c4767c, and none of those paths changed between 37171f65b and 62ddc96cc.
## What changes
N1. F9 ran `$ROOT$NODE` as root (`case1-accept.sh:298` at 37171f65b) before F10 checked that `--node` is
the pinned path. Duckworth offered two fixes: F9's parse through `AS_USER`, or F10's path check first. I
took the path check, plus one more:
- F9 now runs node only when `--node` equals `/opt/mosaic-stack/node-<--node-pin>/bin/node`.
- Then `not_root_only` walks from that file up to `/`. Each entry must be the running user's (root's on the
host) with no group or other write, the rule `case2-unit.sh` `root_only` applied when it rendered the
units. A symlink fails it, because its mode is 777. The first entry that breaks the rule goes in the
evidence.
I didn't run the parse through `AS_USER`. That would hand a check about a root file to the stack's user,
and a process running as `mosaic` shares that node's uid. Whether it could change what node prints then
turns on the host's ptrace setting, and the root-only check doesn't depend on that. Sage agreed in
sage-1877. Rechecking ownership costs one loop and doesn't depend on
how the host was installed.
N2. The header (:9-15) and the `docs/TOOLS.md` entry now list what root runs: `systemctl show` and
`is-enabled` (F1, P11), `curl` on the loopback healthz (F2), `sudo -l` (P1, P3), `sudo -u jarvis bwrap
--version` and `hostcheck.sh --check` (P9), and node for F9 under the rule above.
N3. F6 matched `--claude` by prefix, so `/opt/mosaic-stack/claude-<pin>/../claude-2.0.9/...` passed the
path check. F6 now fails on any `..` segment, before it runs `--version`. F10 compares for equality, so
it needed no change.
Row 195's BUILD.md gets a correction entry for deviation 6. Row 195's DESIGN.md is unchanged. Its "either
works" wording at `DESIGN.md:336-338` is superseded by gosalyn-0087 (root writes `dev.args` at W10), and
it stays in place: an approved design isn't edited (sage-1877).
## Tests
`packages/cli/tests/case1-accept.test.mjs`:
- The node stub touches `node-as-root` in the state directory when something runs it outside the fake
`sudo` (which sets HOME to the stack's home). `host()` sets the node file's directories and the scratch
root to 0755, so the umask can't fail F9.
- "every check passing" also asserts that root ran the pinned node.
- New: "F9 won't run --node as root off the pin path or with a writable directory above it". Another node
path fails F9 and F10, with no root run. A group-writable `bin/` and a symlinked node each fail F9
alone, with the bad path in the evidence and no root run.
- The F6 test gains a `..` case: a path under `claude-<pin>/` by prefix that names 2.0.9's binary, which
prints the pin. F6 alone fails, with "has a .. segment".
That makes 23 tests. All 23 pass on Sage's slot (results below).
## Mutants
The twelve from row 195 still apply, unchanged. These four are new, each one exact replacement:
| # | Mutant | Expected killer |
|---|---|---|
| 13 | F9 runs any `--node` (the pin check becomes `true`) | F9 won't run (another node) |
| 14 | F9 skips the ownership walk (`bad=""`) | F9 won't run (writable `bin/`, symlink) |
| 15 | `not_root_only` checks only the file, not the directories above it | F9 won't run (writable `bin/`) |
| 16 | F6 accepts a `..` segment | F6 stub, path and `..` |
The brief's gate asks for a mutant for each of N1 and N3. Mutants 13-15 are N1's, and 16 is N3's.
## Slot plan
Same shape as row 195's steps, on this row's tip: the single test file, then the sixteen mutants, then
the gate (the cli suite and every `scripts/test-*.sh`), each behind the busy checks and chained with
`&&`. The scripts are in `/mnt/storage/scratch/darkwing-245-out/`.
## Slot results (grant sage-1961, 2026-10-21)
All three steps ran on 0590a29fc with node v26.8.1, launched from `/`. Both busy checks were CLEAR
before each step.
- Singles, 11:27:46-11:27:59Z: `case1-accept.test.mjs` passed 23, failed 0.
- Mutants, 11:28:10-11:31:20Z: all 16 killed, each with exit 1. The runner restored the script, and
its sha256 875bb5b7 matches the committed blob. Rows 1-12 died as in row 195. 13, 14 and 15 died on
"F9 won't run --node as root off the pin path or with a writable directory above it". 16 died on
"F6 fails on a stub that prints nothing, on a path outside claude-<pin> and on a .. segment".
- Gate, 11:31:20-11:42:51Z: the cli suite passed 664, failed 0, skipped 0. Every `scripts/test-*.sh`
exited 0: auth 15, conductor 17, config 31, discord 78, extension-package 18, foundation 44,
land-work 86, queue 28 (its node part 156/0), release 14, task 98. That's 429 selftests, none failed.
- Afterwards no process had its cwd in the worktree, the out dir or TMPDIR, and the
`darkwing-r245_default` network was gone.
Logs: `/mnt/storage/scratch/darkwing-245-out/` (`step1.log`, `mut.out`, `mut/m1-16.log`, `gate.out`).
+107
View File
@@ -0,0 +1,107 @@
# Row 196 build evidence
Base 4d26003f7. Build authorized sage-1371, design approved duckworth-0009/0015.
No live units, live ports, credentials, real second uid or live state touched.
Single test files only so far. Suites and mutant batches require Sage's slot.
## Cleanup ruling, sage-1382 (2026-10-20T19:39:30Z)
The first scratch test proved Node/libuv unlinks a final-path replacement before
JavaScript close callbacks. Evidence: evidence/tester-cleanup-blocker.txt.
Duckworth's option 1 (523a6bddf) was then verified in a standalone scratch test:
bind temporary.sock, record dev/ino, rename to tester.sock, connect successfully,
replace final inode, close. Temporary name absent and replacement survived.
Sage chose option 1. The code binds a unique temporary name in the checked 0700
parent, chmods/verifies the socket, records its dev/ino and renames to tester.sock.
The temporary basename has the same length as tester.sock. libuv remembers the
removed temporary name. The close callback lstat/checks the final dev/ino before
unlink. The final-path replacement test was RED with direct bind and GREEN after
rename. It also asserts no temporary entry after bind or close. M24 will bind the
final path directly and must fail this test.
Residual: a hostile same-uid process can replace a path between lstat and unlink
(or between the final-path check and rename), or recreate the temporary name
before libuv closes it. The checked private parent excludes other uids, not the
instance's own uid. These same-uid races are outside this row's guarantees.
## Development checks
Evidence files retain the red/green and diagnostic output. The initial concurrent
peer test exposed that Node pauses a Socket passed through spawn stdio. The caller
now resumes it after helper exit, with bidirectional post-helper traffic asserted.
No product commits until applicable suites pass. Final candidate and counts follow
after implementation and authorized verification.
## Gate stop, 2026-10-20T19:57:01Z
Gate run 1 stopped at bus 463 pass/1 failure. The row164 reviewed byte pins needed
the intentional row196 human-walk/nonce and Console initial-spawn nonce changes.
Only those two pins were updated, retaining human-cli and CLI transport pins,
literal path recovery and channel plug-in import bans. Focused test passed.
Run 2: bus 464 pass/0 fail. WebUI stopped at two failures in tester-browser.test.mjs:
it still formed TCP URLs from the now-Unix listener address. Script counted 269
passing top-level cases before interruption and 2 failures. No final WebUI summary.
CLI and mutants did not run. Node24 peer 3/3 and principal 4/4 passed separately.
Sage received clear (gander-0023). The failures are fixture migration omissions,
not timing failures from Launchpad's overlapping single T56 run. No product commit.
Focused repairs: the browser fixture fulfills synthetic-origin requests through
CDP from the scratch Unix listener, with no TCP listener, proxy or product relay.
The two tester browser tests pass. Self-review also found a FIFO receipt could
block open before fstat; O_NONBLOCK now makes it refuse promptly (RED timeout,
then interpreter 5/5 GREEN). Missing/non-regular helper now refuses construction
before opening broker.sock (RED missing refusal, then peer reader 4/4 GREEN).
Renewed slot requested in gander-0024; no suite or mutant rerun yet.
## Manifest preparation while the slot is occupied
sage-1409 accepts the CDP fixture repair and startup refusals. MANIFEST.md gives
each its own item, including the old/new human.mjs and WebUI bus.mjs sha256 values
and rationale for the row164 re-pin. PRODUCT-FILES.json records current product,
test and operator-document hashes, explicitly as an uncommitted draft.
Self-review also found helper shutdown returned before child close. The focused
test was RED (evidence/peer-shutdown-red.txt), then GREEN after reader.close awaits
all killed helper close events and server.close awaits reader.close. M27 removes
the wait. M25 removes FIFO nonblocking open and M26 removes helper preflight.
All three are prepared but unexecuted, reported to Sage in gander-0025.
After this change, individual Node 24 files pass: peercred 5/5 and principal 4/4.
Node 26 peer-transport is 3/3. Their logs retain the full results. No suite or
mutant batch ran during the hold. sage-1416 puts Gander after Launchpad, Darkwing,
Rocko and row 192; Sage must send go before the renewed batch starts.
The consumer audit found one additional conversion defect in the files already
changed: proof insertion stopped at --tester-port but not --tester-socket.
Pointer argv therefore differed from rendered unit argv when conversion also
supplied a proof pair. The extended row196 release case reproduced the exact
order mismatch, then passed after adding the socket flag to that insertion
boundary. The proof-file no-read check still passes. M28 restores the defect.
No batch ran. Shell syntax and diff whitespace checks pass. Final prepared
mutant count is 28, pending Sage's go.
## Static review completion, sage-1425
The changed verifier path now has a real Client refusal test. A completed proof
refusal reaches the client as human-required; helper failure is peercred-required.
No broker request dispatches. peer-transport is 4/4. Client code is unchanged:
an abrupt close, timeout or transport error without a response is outcome-unknown.
The scratch base/candidate comparison returns human-required for both. It does
not reproduce Quackerjack's exact failing instance; the manifest names this limit.
Review also found release did not check tester path length until after service
stop. The focused long-path activation fixture reproduced it, then passed with
the new preflight. Existing unsafe parent type/uid/mode checks also precede stop;
the fixture verifies no start/stop calls for unsafe mode. M29 removes the check.
Legacy tester-port-invalid wording is restored to avoid an unrelated contract
change. Review covered all changed trust paths and consumers; no further code
changes are planned before the batch. M1-M29 remain prepared and unexecuted.
2026-10-21 sage-2063: driver uses only node --check for syntax validity; runtime test failures count as kills, and the r2 driver records GNU timeout process-group TERM/KILL exits so blocked descendants cannot survive. No rerun before next clear.
2026-10-21 r2 stop 2: base killer invocations 14/0 under 30 seconds each; M23-M27 killed, M28 survived due rebase making its generic mutation target the earlier SSO insertion. No M29 or golden suite run. Every restored hash and Git status clean; leftover process sweep count 0. Slot returned to Sage in gander-0035. Full per-step counts, base/mutant times and kill table in r2/run2/. Awaiting ruling before any fix or rerun; third stop goes to Mos.
2026-10-21 sage-2072 product test: added the merge-scoped SSO/socket conversion case without --send, asserting pointer argv order, rendered unit argv order, status agreement and no secret-file reads. Removing socket from sso_for_target is its planned RED case (M28b). Focused RED/GREEN remains pending the busy gate: Herb's suite was active at the preflight, so no test started. Runtime code unchanged. Mos mos-1197 owner-subject ruling recorded in queue-252/BUILD-NOTES.md.
2026-10-21 sage-2072/2077 driver: unique string-target guard preflights every definition before any tests and before creating scratch state; M28 names without_proof, M28b names kept, M5 names the parse catch, M25 names append flags, M29 uses the unique full line. Static audit accepts all 30 definitions; zero/duplicate targets refuse. Against 64539c96b, M5 old/narrowed bytes match; M25 differs and joins the next batch. No mutation tests ran in this static check.
+332
View File
@@ -0,0 +1,332 @@
# Row 196: the human boundary across uids and humans
Gander, 2026-10-20. Revision 3, recording approved r2 and N5. Gitea #1694, P19 333.
Base: `96f243faf`, branch `gander/row196`. Design approval latest-by 2026-10-22, landing
latest-by 2026-10-24 and before federation R3. This document authorizes no live action.
## 1. Outcome and authority
Other Linux users must not reach the tester's data or writes. A bus human proof must describe
the process that actually connected. Console user calls must also pass the human walk and
come directly from this bus tree's Console. Row 193 and federation R3 use that one verifier.
This is not isolation from a hostile process on the instance's own uid. Keeping the human
terminal CLI means that an unmarked process on that uid still has the owner's authority.
Process ancestry cannot prove that a process was never an agent. Sections 4 and 8 state this
limit without treating it as an enforcement guarantee.
Inputs: COMMON at `53bb59462`, the row brief at `96f243faf`, Q175 sections 1, 6 and 8,
federation r2 at `60b9b112a` section 3.6, Duckworth's B1-B5 at `b402197ff`, row 190's
DESIGN.md and the row 190b brief. Q175-Q178 and the MVP plan set the order. Row 190b is on
hold pending the host decision. This row does not assume that a separate uid is already live.
Sage's `sage-1343` accepts the design direction and requires Duckworth to rule on the peer
credential implementation. `sage-1345` keeps the control board inventory-only. Morgana's
`morgana-0003` supplies the principal contract in section 4. Sage registers this session.
Mos accepted removal of direct tester browser access in mos-1015, relayed by sage-1363.
Agents test through the private socket. Any later browser view is an authenticated relay
behind SSO, designed and reviewed in its own row. This row adds no relay.
## 2. Inventory from code, not a live probe
"Same uid" includes untrusted local processes. "Other uid" excludes root, which is outside
this boundary. SSO below means Q175 is active on that instance, not merely installed.
| Entry | Same uid today | Other uid today | Effect of SSO and this row |
|---|---|---|---|
| Main Console, default 7332, bare serve default 7330 | Loopback reads. With preview/send, matching Host, header and origin can write as owner | Same TCP reach and header forgery | Q175 SSO verifies login and refuses non-public Host except GET /healthz. This row does not change public ingress |
| Tester TCP, default installation 7333, optional per instance | Reads and the tester instance, route, name and chat writes | Identical reach, no uid check | Q175 deliberately leaves tester separate. This row replaces TCP with a private filesystem Unix socket |
| `<dataRoot>/bus/broker.sock` | Connects, then needs cap or proof. Claimed human PID is not the socket peer | Normally refused by bus/ 0700 and socket 0600 | SSO changes nothing. Add peer uid and PID binding, retain file checks |
| Human wrapper to tree's human-cli.mjs | Owner if CLI path, nonce and ancestry pass. Agent ancestors refuse | Cannot connect to protected socket, and proof uid refuses | Preserve terminal owner path, add peer binding. SSO does not authenticate terminal users |
| Tester CLI to broker.sock | Tester if its proof passes and bus.tester is exactly true. T3 agents are allowed, managed runs are not | Filesystem modes normally refuse | Same tester policy, plus peer binding. No owner/user upgrade |
| `<dataRoot>/bus-host/launch.sock` | Requires run cap and launcher checks | bus-host/ 0700, socket 0600 | No human proof endpoint. Unchanged |
| `<dataRoot>/bus-host/git.sock` | Git capability protocol and broker authorization | bus-host/ 0700, socket 0600 | No human proof endpoint. Unchanged |
| Discord connector | Outbound Discord gateway/REST, no inbound local listener. Local process could tamper with same-uid files/process | No local port to call. File separation depends on uid and modes | Existing guild, user, channel, bot and webhook checks remain. Discord is not a new bus human transport |
| Row 190 additions | Separate configs, roots, unit names, wrappers and Console ports, on one uid | Console/tester TCP still reachable | Adds no new class of listener. Row 196 derives its socket per dataRoot. Row 190 alone supplies no hostile-process isolation |
| Control board, default 7331 | Unauthenticated transcript/board reads, POST /api/reply and /api/seen after request checks | Same loopback reach. A local client can supply matching request headers | Independent development service, outside instance SSO. Inventory only per sage-1345. Its fix needs another row |
Evidence: `packages/webui/src/{cli,serve,bus}.mjs`, `packages/bus/src/{server,runtime,human,
tester,store}.mjs`, `packages/cli/src/{launcher,git-socket,transport}.mjs`,
`packages/discord/src/{gateway,authorize}.mjs`, `packages/control-board/src/serve.mjs`.
Store checks bus/ with `safe(directory,true)`, including owner, symlink and mode checks.
The dataRoot check itself does not require 0700 today. Do not describe that as a checked mode.
## 3. D1: private tester HTTP socket
Add `serve --tester-socket`, a boolean selecting `<dataRoot>/console/tester.sock`. No arbitrary
path or new system config field. Create console/ as 0700. Refuse a symlink, wrong owner,
non-directory or any group/other bits on an existing console/. Refuse a pre-existing socket
or other entry rather than deleting it blindly. A stale socket needs an operator's diagnosis.
Refuse a path longer than Linux's 107-byte pathname capacity before binding.
Bind HTTP on the Unix path, set socket mode 0600, then verify type, owner and mode before
declaring ready. The private parent covers the bind-to-chmod interval. Cleanup only the owned
device/inode, as broker.sock does. Never open a TCP compatibility bridge or an abstract socket.
Readiness failure closes the main listener too, rather than leaving a partially started Console.
The tester handler keeps its fixed principal, header/cookie refusals and exact write list.
Use a fixed logical origin `http://mosaic-tester.invalid` for URL and same-origin checks. It
does not resolve that name. Clients send that Host and Origin plus the existing Fetch Metadata
when required. A Host string is not authentication. Unix socket access supplies that boundary.
The main listener still refuses x-mosaic-tester. No tester /auth route exists.
`bus.tester` remains Jason's switch. Socket reach never enables tester authority by itself.
Turning it off must refuse every tester bus call. This row adds no user or owner capability
to the tester. Tester reads retain today's scope, including any local Console reads that are
not bus calls. This row does not turn bus.tester into a separate HTTP read switch.
### Consumers and migration paths
| Consumer of 7333 | New path |
|---|---|
| Agent tester HTTP tools and curl probes in authorized window packets | `curl --unix-socket <dataRoot>/console/tester.sock http://mosaic-tester.invalid/...`, with the fixed Origin for writes. Add one repository helper for bounded tester HTTP requests so callers do not copy path/origin logic |
| `scripts/mosaic ... --tester` | Already uses TESTER_CLI and broker.sock, not TCP. Keep its verbs, output and switch |
| Browser tester page in row 176 and later window checks | Ordinary browsers cannot navigate a Unix HTTP socket. Replace automated tester HTTP checks with the socket helper. Browser visual QA uses scratch fixtures only. No local TCP relay is added |
| `webui-service.sh` render/install and installed unit | Carry --tester-socket for new copies. Refuse a target without support. Units running old copies retain their old flags until a window |
| `stack-release.sh` activate, plan, rehearsal and rollback | Add --tester-socket. Replace inherited --tester-port N only when explicitly requested. Refuse incompatible inherited flags before stopping units. Plan names the transport and every dropped pair. Rehearsal uses a scratch socket |
| Gate/window Console checks, including agents' console-check scripts | Extend checks to use scratch Unix HTTP and expect no tester TCP bind. Sage owns live packet updates, including Q175 phase 2's old --tester-port 7333 instructions |
| `docs/TOOLS.md`, webui/cli/bus READMEs and examples | Document socket invocation, fixed origin, loss of direct browser navigation and per-instance path. Historical window receipts stay unchanged |
Repository search found no non-test hardcoded 7333 caller in tools/ or the harness code. The
active windows and external seat commands still need Sage's consumer audit before re-pin.
The build manifest will record the full search, rather than treating this draft as that audit.
## 4. D2: one verified principal per bus request
Internal interface: `verifyPrincipal(request, connection) -> {kind,business,id}` or refusal.
Kinds are owner, user and tester. The connection metadata is supplied by the trusted server,
never deserialized from JSON. No role or authority is returned. The broker looks up live policy.
1. Read and validate the bounded envelope first. For human, tester and user proofs, read Linux
SO_PEERCRED on that accepted Unix connection before dispatch. Require a valid peer PID
and the bus uid. Refuse unavailable credentials, unsupported platform and errors. Cap
traffic keeps its capability protocol behind the validated 0700 directory and 0600 socket.
2. Require exactly one cap, human, tester or future user envelope. Human/tester/user proof.pid
must equal the kernel peer PID. Verify startTime, nonce and CLI path against that process.
Keep the second process read after the walk. Missing, changed or exited processes refuse.
3. Owner terminal: run the full existing human walk. Map the business to businesses[b].human.
No supplied human id, role or uid selects the principal. Keep today's terminal invocation.
4. Tester: run verifyTester, retaining its deliberate difference from the human walk. An agent
seat may be tester. A managed-run marker or registered managed ancestor refuses. Then check
bus.tester. The full human walk must never be weakened to admit a session user.
5. Session user: use the full human walk with the path-bound user-cli entry and its own nonce
variable, MOSAIC_BUS_USER_NONCE. Parameterize the nonce reader without admitting a human
or tester nonce as a user proof. Require its immediate parent to be the same tree's resolved webui/src/cli.mjs,
argv[2] `serve`. Check parent uid, startTime and ppid again before binding. Both child and
parent remain alive for the call. A different checkout or wrapper parent refuses.
6. For user calls, the Console puts its mapped access-file user id in the verified proof's
user field, as federation 3.6 specifies. The id comes only from claims Q175 verified for
this request's live session, never from an identity cached past that session. It never
takes that id from a form field or verb
args. Return kind user, proof.business and proof.user as id only after the provenance
checks. R3 then validates that mapping and the live grant. Bad access.json denies users,
not the terminal owner. The verifier does not implement access-file policy.
7. Owner Console calls keep the verified Q175 owner subject path. Under SSO only a subject
in console.sso.subjects reaches owner bus calls. They use human-cli and the same peer/walk
checks. A terminal owner still needs no SSO token. No anonymous main-listener fallback.
The bus verifies which process supplied the session identity. It relies on that Console's
Q175 implementation to have verified OIDC. It does not independently verify an ID token in
this option. A provider/sub field alone, from another process, is not enough.
The Console must spawn user-cli directly with its nonce already in the launch environment.
It must not use human-cli's existing self-respawn pattern, which makes the CLI's parent a CLI.
Apply the same initial-nonce spawn to Console human calls for consistent tests. Terminal
human-cli can retain its self-respawn since no Console-parent condition applies there.
Never strip agent markers from inherited environments to make a walk pass.
The server binds an internal cap for this request, dispatches, and disconnects in every
success/error/timeout path, including asynchronous task and web calls. It never returns the
cap or a reusable principal handle. A proof copied by another process has a different kernel
peer PID and fails. One verified CLI may make only its one request on its one connection.
The session may hold s.user for that request. No PID or principal cache survives the connection.
### Fit with rows 193 and R3
Row 196 owns peer metadata, proof/walk verification and the internal principal interface.
It supplies the Console provenance verifier and tests before R3 adds user-cli and user sessions.
R3 owns access.json mapping and the bindUser integration, using this interface. It must not
add a second ancestry implementation. Row 193 adds viewer and share policy to the same live
grant check. An owner id is the business human. A user id is the access-file id, with stored
owner string `user:<id>`. Neither row takes role from the proof.
No live grant, unknown subject, duplicate owner/user mapping and invalid access file all
deny user binding through R3's policy. Per-request authorization, access.reload and the
30-second recheck remain R3's responsibility. Share visibility uses that check in row 193.
Binding provenance is not a grant and never widens one. R3's user dispatch must run before
names falls through to #agent. R3 must not land before this contract and its tests are green.
Gizmoduck's gizmoduck-0015 and Gander's gander-0007 agree on this split. R3 keeps the reserved
user-name mutant. Row 196 tests kind separation even if a user id resembles the owner name.
### OIDC alternative
The bus could verify the signed ID token itself and map its issuer/sub. That reduces reliance
on the Console's subject assertion, but requires JWKS/provider handling, nonce or challenge
binding, replay handling, login and logout/session lifetimes in the bus. Reusing an ID token
on every request either creates a replay credential or expires a Console session with the
token. A bus-issued session needs a separate lifecycle design. It also leaves the terminal
owner and hostile same-uid limit intact. Recommend the Console provenance contract for this
slice. Duckworth approved this option in duckworth-0006, with the live-session condition above
and the requirement to carry section 8 R2 into P19 333 word for word.
## 5. D3: obtaining kernel credentials, O1 approved with conditions
All options must bind the accepted connection, not a PID a caller picked. None gets a silent
fallback to the current proof. Sage required this comparison. Duckworth selected O1 in
duckworth-0006, with the interpreter, proof-only and image conditions below. O4 remains the
fallback if Python is unavailable, and needs a revised build manifest before use.
| Option | What it proves and costs | Recommendation |
|---|---|---|
| O1 Python stdlib helper on inherited accepted socket | Node passes the accepted net.Socket as fd 3 using documented spawn stdio stream passing. Python socket(fileno=3) reads SO_PEERCRED and returns only pid,uid,gid on stdout. No socket reads, no shell, no credential values. Adds Python to bus hosts and any future bus image, with a process per admitted proof connection. Copies carry the helper and depend on the system interpreter | Selected by Duckworth, with conditions below |
| O2 /proc/net/unix plus inode and /proc/PID/fd scans | The table identifies Unix socket entries, not an authenticated peer PID for each accepted connection. Finding a process holding an inode is racy, may have several holders and can be unreadable. The server endpoint's inode is not the client's inode | Reject as a replacement for SO_PEERCRED. No claimed proof PID may choose the scan target |
| O3 plain Node fd passing or abstract sockets | A private inherited IPC channel can authenticate a child that this parent spawned. The bus did not spawn the independently started Console or terminal CLI. Moving the bus into the Console's child tree changes lifecycle and ownership. An abstract name supplies no filesystem mode boundary and no peer uid check by itself. Node's public net API supplies no SO_PEERCRED accessor | Reject as a drop-in. A larger bus-spawned transport design could use fd capabilities, but is outside this slice |
| O4 small compiled helper or Node-API addon | Calls getsockopt directly, with no Python runtime. A helper can use the same inherited fd. Adds compiler/build closure and platform artifacts to stack-copy and containers. An addon also needs a supported way to obtain the accepted socket fd and Node version coverage | Viable alternative if Python is refused. Prefer a small helper over undocumented Node handles |
For O1, pin the system interpreter `/usr/bin/python3`, resolving its system symlink to an
absolute ELF binary. Require root ownership and no group/other write on that executable and
its path components. Refuse scripts, PATH shims and any resolved path under an OS account's
home, including /home and /root. A pyenv shim can choose an interpreter before -I -S applies.
No PATH lookup, caller-selected interpreter or helper. Invoke the resolved binary with
`-I -S`, a minimal environment and the absolute helper path in the same pinned copy.
Activation preflight records the interpreter's resolved path, version and file identity/hash
in its gate evidence. The bus validates and pins the resolved executable at each startup
before opening broker.sock. The activation gate must report the same interpreter used by
that bus. Missing, unsafe or changed resolution during that activation refuses it, never
falling back to PATH. No secret values are recorded. Test system-owned interpreter checks
through injected metadata.
N5 decision: a later bus restart repeats the safety/version/support checks and accepts a
safe system interpreter installed by an OS upgrade. It does not compare against an old
activation hash. Before opening the listener, append a startup receipt with timestamp,
copy path, resolved interpreter path, version and file identity/hash to
`<dataRoot>/bus/peercred-starts.jsonl` (0600 in the checked 0700 bus directory). Refuse an
unsafe receipt file or failed append. Never edit the old activation evidence. The startup
receipt identifies the binary now in use, so the old receipt remains a historical fact.
If an upgraded interpreter fails the checks, startup fails closed until a safe supported
system interpreter is restored by the operator. There is no shim or O4 auto-fallback.
Operator re-pin after an interpreter change: in the next authorized window, prepare the
next reviewed copy with `scripts/stack-copy.sh copy NEW`, then run
`scripts/stack-release.sh activate NEW BUSINESS --tester-socket` with that instance's existing
flags and selector, first with --plan. NEW must differ from the active copy because activate
refuses already-active. Record the interpreter in the new activation evidence and compare it
with the bus startup receipt. A routine OS update alone does not authorize a re-pin or unit
restart. A bus restart does not rewrite config, pointer or activation history.
Preflight helper and SO_PEERCRED support too. The helper lives under packages/bus/src so
stack-copy's closure includes it. Copies remain immutable. Document Python >=3.10 on bus
hosts. Today's Containerfile builds the worker image and runs run-agent.sh, not the bus.
It needs no Python change for this row. A future bus-host image must explicitly include
the pinned system Python or use reviewed O4. No install through the node_modules symlink.
Pause each proof connection while reading credentials. Limit outstanding helpers
to 16, timeout to one second, output to one strict JSON record of at most 256 bytes. Refuse
nonzero exit, extra output, malformed integers, missing fd and timeout. Validate pid, uid and
gid as safe integers, pid >=2 and uid/gid >=0. Only pid and uid select the principal. gid is
validated but grants no authority. Kill and reap helpers on shutdown. Cap envelopes never
spawn this helper. Reading the proof envelope first is sound because SO_PEERCRED describes
the peer at connect time. Keep the accepted Socket object owned by its connection closure,
not a shared current-fd variable. Only the helper's duplicate fd is closed when it exits.
References: [Node stdio stream passing](https://nodejs.org/api/child_process.html#optionsstdio),
[Node Unix socket support](https://nodejs.org/api/net.html#ipc-support),
[Python socket and getsockopt](https://docs.python.org/3/library/socket.html),
[Linux Unix credentials and permissions](https://man7.org/linux/man-pages/man7/unix.7.html),
[Linux proc network table fields](https://man7.org/linux/man-pages/man5/proc_pid_net.5.html).
These support the APIs. The connection-binding behavior still needs T2 on supported Node versions.
## 6. Migration and rollback
No config schema or store schema changes. Old copies and units keep working until each
instance re-pins. No new verifier is loaded into a running old broker. Do not pair a new
Console with an old bus and call it protected. Same-tree paths intentionally refuse mixtures.
The authorized window must pin bus, Console and human wrapper to one candidate. New tools
offer `activate ID BUSINESS --tester-socket`. Plan must show the old tester-port pair removed
and the new socket flag added, and tell Jason that direct tester browser navigation is lost.
Mos accepted that change in mos-1015 (sage-1363). Agents test through the socket. A later
authenticated browser relay behind SSO belongs to its own row. Without the explicit
conversion, a candidate that no longer
serves tester TCP refuses inherited --tester-port before stopping anything. New serve refuses
--tester-port with a migration error. It never silently changes that flag's meaning.
Rollback to a copy lacking --tester-socket drops tester enablement and reports it, as today's
tools drop unsupported flags. It does not turn the socket back into TCP. The old main Console
and owner CLI can run with their known old limits. A failed activation's recovery may restore
the old saved unit, including its TCP listener. Report that recovery as restoration of the old
security state, never as a hardened deployment. After rollback, R3 and second-human access are
not accepted until prerequisites are restored. Older operator tools cannot manage the new
flag safely. The window must use the reviewed candidate tool blob for rollback as well.
The Q173 window supplies real uid-denial evidence and the final consumer audit. No second uid,
live socket, unit, config or secret is touched in this row's tests. Row 190b or a separate host
still owns cross-instance file isolation. It is not replaced by SSO or this socket change.
## 7. Acceptance tests and mutants
All fixtures use scratch roots and ephemeral sockets, no live 7332/7333, public origin or
provider. No real second uid. Foreign uid checks use injected metadata and name that limit.
Use short TMPDIR paths so the longest Unix socket path stays under 108 bytes.
| Test | Required evidence | Mutant killed |
|---|---|---|
| T1 private tester | HTTP over scratch socket reads and writes as tester only. parent 0700, socket 0600. No tester TCP listen. Wrong Host/origin and auth cookies refuse | M1 bind loopback TCP as well, M2 remove socket mode check, M3 accept owner cookie |
| T2 real peer binding | At least 8 concurrent proof connections from distinct scratch client processes, each with its own live proof, plus a copier racing one live CLI. Barrier the helpers so connections overlap. Each genuine call must bind its own peer PID and pass, the copier must refuse. Repeat on Node 24 and 26. A lower-level helper fixture exchanges bytes both ways on the accepted socket after the helper exits, and each real bus call receives its response after helper exit. Helper failure cases all refuse | M4 use proof.pid as peer PID, M5 continue after helper error, M20 give the helper another pending connection's socket |
| T3 uid/path refusal | Inject foreign peer uid, wrong file owner/modes, symlink parent, existing socket and long path. Inject interpreter script, user-owned ELF, home path, writable ancestor and missing executable. Refuse before dispatch, no repaired paths. Unsafe pid/uid/gid and extra helper output refuse | M6 ignore uid, M7 accept unsafe parent |
| T4 ancestry | Agent env, binary and registered launch above owner/user refuse. Missing/reused PID, changed parent and cycles refuse. Clean terminal owner works | M8 omit walk, M9 omit identity re-read |
| T5 Console parent | Direct CLI child from exact tree Console fixture works. Same CLI under shell, other copy, wrapper, agent ancestor or CLI respawn refuses | M10 remove direct-parent check, M11 accept any Console basename |
| T6 principal shape | No human id or role from request body. Exactly one proof. No returned cap. Disconnect on synchronous and async failure, timeout and close. Tester agent allowed, managed tester refused | M12 accept extra identity, M13 omit disconnect, M14 use tester walk for user |
| T7 consumer migration | Render/plan/activate/rehearse fixture carries socket flag in unit and pointer. Explicit conversion removes port. Unsupported/inherited mismatch refuses before unit calls. Rollback disables unsupported tester, never opens TCP | M15 keep inherited tester port, M16 restore TCP on downgrade |
| T8 gates and identity | bus.tester false refuses tester calls. SSO owner/user wiring uses only claims verified for that request's live session. Expired/logged-out or replaced session cannot reuse an earlier mapped id. User kind never becomes owner from its id. R3 integration maps subject, denies missing/invalid grants and owner overlap, broken access file leaves terminal owner working | M17 bypass tester switch, M18 turn user kind into owner by id |
| T9 isolation limits | Two scratch instances have different sockets and matching tree paths. Foreign-uid stand-in refuses. Record that no real uid boundary was exercised | M19 choose default instance socket |
| T10 interpreter and cap path | Activation evidence records the same pinned root-owned system ELF that the bus uses. Scripts, home paths and unsafe owners/modes refuse. Simulated OS replacement followed by startup accepts a safe replacement and appends its actual identity without changing the old receipt, refuses an unsafe replacement or unsafe/unwritable receipt. Cap calls complete without invoking the helper, proven with a helper spy that fails if called | M21 bypass interpreter safety, M22 invoke helper for cap traffic, M23 retain stale interpreter identity after restart |
Tests for new policy stay in the bus, WebUI and CLI packages. R3 provides the real user/grant
half of T8 in its candidate, with its own gate. This row tests the shared verifier's user
provenance without shipping placeholder user access. Mutants run on scratch copies,
restore the original bytes and verify hashes. Two failures in a gate run stop and are reported.
Single test files run freely. Ask Sage for the slot before any suite. Final acceptance is
Duckworth's approved manifest plus Sage's bus, WebUI, CLI and scripts/test-*.sh gate.
## 8. Closure, limits and review decisions
R1. Same uid remains trusted OS authority. A hostile process may detach, clear its markers,
modify argv, read same-uid pipes or capabilities, edit policy files, or interfere with the
Console where host policy permits. SO_PEERCRED proves the connecting process identity at
connection time. It does not prove human intent, protect memory, or track a socket passed to
another process. Ordinary agent children fail the walk. Hostile detached agents are not
claimed contained. Separate uids narrow cross-instance reach, not reach within one instance.
R2. The Console provenance option trusts verified code and process metadata, not an independent
bus-side OIDC exchange. That trade must be approved, not hidden behind the phrase zero trust.
R3. A Unix tester endpoint removes direct browser navigation. No bridge is in this design.
The 7331 development board remains a separately reported reachability and impersonation risk.
P19 333 can record the following after the implementation gate and authorized re-pin:
"Tester ingress is private to the instance uid. Bus human proofs are tied to the actual
Unix peer and retain the human walk. Session-user calls require the same-tree Console as
direct parent. Q175 phase 2 separately protects main Console ingress with verified OIDC.
Hostile processes on the instance uid, root, and the independent development control board
remain outside these guarantees. Cross-instance file isolation requires the uid/host gate."
Duckworth requires that P19 333 also carry R2 word for word:
"The Console provenance option trusts verified code and process metadata, not an independent
bus-side OIDC exchange. That trade must be approved, not hidden behind the phrase zero trust."
Do not mark the historical task fully closed on a design or commit. Closure needs live
Q175 phase 2, this row's re-pin and the recorded residual accepted through Mos. A promise of
hostile same-uid isolation would require separate OS authority for agents and a new design.
Review receipt: duckworth-0006, notes at `86be181a2:agents/duckworth/work/row196/REVIEW.md`.
Q1 selects O1 with the conditions incorporated in section 5. Q2 approves Console provenance
with the live-session and unchanged P19 R2 conditions. B1 adds concurrent T2, M20 and socket
survival checks. N2's detached-Console limit remains in R1. N3's gid validation and N4's
operator window warning are included. Mos's tester-access ruling is recorded above. No
product code begins before design approval.
Duckworth approved revision 2 in duckworth-0009, review at
`cb6d840df:agents/duckworth/work/row196/REVIEW-r2.md`. N5 permits either restart policy.
Revision 3 selects safety revalidation with a new append-only startup receipt, and gives the
operator a re-pin path that respects the existing already-active refusal.
+121
View File
@@ -0,0 +1,121 @@
# Row 196 review manifest
Status: draft, not an acceptance claim. Candidate product files are uncommitted.
Base: 4d26003f731a6eec439df6a48cacc3ef482a59c8. Design r3 approved by
duckworth-0009/0015 and sage-1371. No merge, deployment or live re-pin requested.
Current path hashes are in PRODUCT-FILES.json, marked as an uncommitted draft.
Final candidate commit, exact changed-file hashes and gate totals follow after
the renewed suite slot. Sage owns that slot and the final gate. Duckworth reviews
this manifest before Sage's gate.
## Review items
| Item | Change and acceptance evidence | Mutants |
|---|---|---|
| Kernel peer proof | `bus/src/peercred.py` obtains SO_PEERCRED from inherited fd 3. `peercred.mjs` owns the accepted Socket in each connection closure, bounds helpers and validates all three safe integers. `peercred.test.mjs` overlaps eight distinct callers plus a copier and checks traffic after helper exit. | M4, M5, M20 |
| Interpreter trust | `bus/src/interpreter.mjs` resolves fixed system Python, checks root ownership, safe ancestors, ELF and version, and refuses home paths or shims. Startup appends actual identity; activation compares the fresh receipt. `interpreter.test.mjs` covers safe replacement and unchanged older receipt. | M21, M23 |
| Startup FIFO refusal | Receipt opens include O_NONBLOCK before fstat, avoiding an indefinite FIFO open. `interpreter.test.mjs`, `non-regular startup receipt refuses promptly without waiting on a FIFO peer`, was RED by timeout, then GREEN. Evidence: `evidence/interpreter-fifo-red.txt`, `evidence/interpreter-green.txt`. No live receipt was used. | M25 removes O_NONBLOCK |
| Missing helper refusal | Reader construction refuses missing, symlink or non-regular peer helper before opening the broker socket. `peercred.test.mjs`, `missing or non-regular peer helper refuses at construction before opening the bus`, was RED, then GREEN. Evidence: `evidence/peer-helper-red.txt`, `evidence/peercred-green.txt`. | M26 removes helper preflight |
| Helper shutdown | Reader close kills and awaits helper close events; server close awaits it. `peercred.test.mjs`, `shutdown waits for killed helper close before returning`, was RED, then GREEN. Evidence: `evidence/peer-shutdown-red.txt`, `evidence/peercred-green.txt`. | M27 returns without waiting |
| Shared principal | `bus/src/principal.mjs` checks kernel uid/PID, retains the human walk, separates nonce kinds and requires the exact same-tree Console serve direct parent for users. `principal.test.mjs` supplies process-table refusals. User id is provenance only; R3 owns mapping, grants and user dispatch. | M6, M8-M12, M14, M18 |
| Request lifetime | `bus/src/server.mjs` runs the helper only for proof envelopes and releases transient caps for sync/async success, refusal, timeout and close. `peer-transport.test.mjs` tests cleanup and fails if a cap request invokes the helper. | M13, M22 |
| Private tester ingress | `webui/src/tester-socket.mjs` owns 0700 console directory and 0600 socket; `serve.mjs` preserves tester policy and refuses sign-in headers/cookies. `tester-socket.test.mjs` covers unsafe paths, origin/Host, instance separation and HTTP access. | M1-M3, M7, M19 |
| Owned inode cleanup | sage-1382 selects temporary bind, rename to final, conditional final dev/ino cleanup. `tester-socket.test.mjs` asserts temporary name absent after bind/close and replacement final inode survives close. Scratch proof and remaining same-uid race are in BUILD.md. | M24 binds final directly |
| Tester policy | `bus.tester` remains Jason's switch. Existing `tester.test.mjs` verifies refusals. No user widening, new browser relay or control-board change. | M17 |
| Consumer migration | WebUI CLI, service rendering, release plan/activation/rehearsal and CLI Unix HTTP helper carry explicit --tester-socket. Inherited TCP on a new target refuses before stop without explicit conversion. Unsupported rollback disables tester and reports it. Focused service/release/tester HTTP files cover these paths. | M15, M16 |
| Proof pair ordering | Conversion plus an explicit proof pair keeps pointer and unit argv in the same order: preview, proof pair, tester socket. The focused row196 release test was RED with the old insertion scan, then GREEN after including --tester-socket in its stop list. The proof-file no-read check also passes. Evidence: `evidence/release-proof-order-red.txt` and `evidence/release-proof-order-green.txt`. | M28 omits socket from the insertion boundary |
| Path preflight before stop | Activation checks the final Unix pathname byte length and any existing parent directory's type, owner and mode before stopping units. The row196 release case reproduces a too-long path reaching service stops, then verifies length and unsafe-parent refusals leave start/stop calls unchanged. Evidence: `evidence/release-path-red.txt` and `evidence/release-path-green.txt`. | M29 removes the preflight |
| Browser fixture migration | Test-only CDP interception fulfills synthetic-origin requests from the scratch Unix listener. No TCP listener or product relay. `tester-browser.test.mjs` is 2/2 after the fixture repair, accepted in sage-1409. Evidence: `evidence/tester-browser-focused.txt`. | Product transport removal is covered by M1; browser fixture itself is tested by its two cases. |
All package paths above are under `packages/`. Mutants are prepared in
`mutants.mjs` but none have run as of this draft. Their kill and restoration
receipts must be attached before acceptance. M25-M29 supplement design M1-M24.
## Separate row164 proof baseline update
Duckworth should review this hunk separately, as requested in sage-1409.
Only two baselines in `packages/bus/tests/channel-proof.test.mjs` change.
| File | Previous sha256 | Candidate sha256 | Why |
|---|---|---|---|
| `packages/bus/src/human.mjs` | `b995fc5cff3af8ade4c990d0e3bebf9d83829e00f034ebc9c3dcfd05f252b573` | `0c6d96abe4607cba0b02b0638a2fc217ff8135bac9a47761a0ef015fbe9d3079` | Read the distinct user nonce and parameterize the existing path-bound human walk for the shared verifier. |
| `packages/webui/src/bus.mjs` | `76a1b57858c4aa41071746713a196299264851c18322a6f9869d553f0362d421` | `934ab2f9b6bde72aafe7814758550c283eb25a4f3a23b284021b13718d67178f` | Supply the initial owner CLI nonce at spawn and map peercred-required to HTTP refusal. |
The human-cli and CLI transport hashes stay fixed. Literal shim path recovery,
forged-proof refusal and the channel plug-in proof-caller import ban remain tested.
Focused evidence: `evidence/channel-proof.txt`. These hashes must be checked again
against the final candidate before review.
## Consumer inventory and window
Tester tools use `packages/cli/src/tester-http.mjs` and the selected instance's
console/tester.sock. WebUI tester test fixtures and browser fixtures use scratch
Unix sockets. The stack-release gate and rehearsal carry --tester-socket and
compare the bus startup interpreter receipt. Package READMEs and docs/TOOLS.md
show the Unix path and explicit migration. Direct browser access is lost by
Mos's accepted ruling mos-1015; a future SSO relay needs its own row. Name that
loss to Jason in the authorized window. No candidate is live from this work.
The 7331 development control board is inventory only. Its reads, /api/reply
writes and reachability remain the separate boundary described in DESIGN.md.
Row 190b remains on hold; this work does not isolate hostile same-uid processes.
## Gate history and remaining evidence
Run 1: bus 463 pass, one byte-baseline failure. Run 2: bus 464 pass; WebUI
stopped at its second fixture migration failure, with 269 observed top-level
passes and no final summary. CLI and mutants did not start. Sage received clear
in gander-0023. No timing failures were attributed to Launchpad's T56 overlap.
The two browser failures were repaired and verified in a single file.
sage-1416 delays the renewed slot until Launchpad, Darkwing, Rocko and row 192
finish. No suite or mutant batch starts before Sage's go. Remaining: final
bus/WebUI/CLI suites, M1-M29 receipts, final changed-file hashes,
verified candidate commit/push, Duckworth
manifest review and Sage's gate. No approval is implied by this draft.
Node 24 focused reruns after the shutdown change are complete: peercred 5/5 and
principal 4/4. Node 26 peer-transport now passes 4/4, including client-visible
refusal codes. Full logs are in evidence/.
## Refused caller behavior and static review
The verifier path is changed by this row. A completed proof refusal returns an
explicit protocol error before closing the connection: `human-required` for a
rejected human proof and `peercred-required` for helper failure. The real Client
receives those codes in `peer-transport.test.mjs`, without broker dispatch. No
client code is changed. Abrupt close, transport error or timeout without a valid
response still reports `outcome-unknown`; this row does not convert that case.
Quackerjack's reported failed-proof close was not reproduced against this branch's
base. A scratch comparison loads HEAD's server and the candidate with the same
refusing authenticator and unchanged Client. Both return `human-required`.
Evidence: `evidence/refusal-baseline-comparison.txt`. This comparison does not
claim to reproduce Quackerjack's exact process ancestry or instance. The manifest
therefore claims the explicit codes only for the paths verified here.
Static review covered peer reader and shutdown, runtime startup and failure
cleanup, principal/walk dispatch, tester bind/close, Console spawn, Unix client,
release conversion/recovery/preflight, unit rendering and test fixture changes.
It found the shutdown, proof ordering and preflight defects recorded above.
Each has a focused test and prepared mutant. An unrelated change to legacy
tester-port-invalid text was also removed to preserve the existing contract.
No further implementation changes are planned before the authorized batch.
## Limits carried to acceptance
No real second uid or live instance is exercised. Foreign uid metadata is a
stand-in. The authorized window still owns real uid-denial and consumer audit.
Python is a new bus-host dependency; the worker Containerfile is unchanged.
Restarts validate the current interpreter and append a receipt; old receipts
are preserved. Interpreter changes use the next reviewed copy and preserve
the already-active refusal.
Hostile same-uid processes can read or alter same-uid state, detach or interfere
with pathname checks and Console provenance. Separate uids narrow cross-instance
reach, not reach within one instance. R3 must source the proof id only from
claims verified for that request's live session and reread grants per request.
P19 333 must retain this text unchanged:
The Console provenance option trusts verified code and process metadata, not an independent
bus-side OIDC exchange. That trade must be approved, not hidden behind the phrase zero trust.
+121
View File
@@ -0,0 +1,121 @@
# Row 196 Human Boundary Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.
**Goal:** Bind human proofs to their actual Unix peers and make tester HTTP private to the instance uid.
**Architecture:** The bus reads kernel credentials through a pinned system Python helper only for proof envelopes. One principal verifier keeps the human walk and supplies Console provenance for federation R3. Tester HTTP moves to a private filesystem socket. Release tools migrate the flags explicitly.
**Tech Stack:** Linux, Node 24 and 26, Python >=3.10 standard library, existing Node test runner and shell release tools.
**Spec:** `agents/gander/work/queue-196/DESIGN.md`, approved r2 at 5fe259f16, with N5 restart policy added in r3.
## Global Constraints
- Work in the existing gander/row196 worktree. Plain node_modules symlink. No new package dependency.
- No live files, credentials, listeners, units, second uid or external authentication service.
- Helpers: proof envelopes only, at most 16 concurrent, one second timeout, 256-byte strict output.
- Root-owned system Python ELF at resolved /usr/bin/python3, never PATH or a home/shim path.
- console/ 0700, tester.sock 0600, Unix pathname at most 107 bytes.
- One principal and transient cap per request. R3 owns user access policy and user-cli integration.
- Mos accepted socket-only tester access. No TCP relay or control-board change.
- Single test files run freely. Ask Sage before any suite. Stop and report two failures in a gate run.
- Commit code only after applicable suites pass. Sage owns the final gate and manifest queue moves.
## Review Focus
- Connection overlap and disconnect: helpers must retain the correct Socket object and release capacity on every exit.
- OS interpreter replacement: restart records the actual safe binary, while old receipts remain unchanged.
- Long or unsafe instance paths: refuse before binding or stopping any service.
- Rollback/recovery: distinguish intentional tester disablement from restoration of an old saved TCP unit.
- CLI nonce respawn and session expiry: direct-parent provenance must survive correct Console spawning and refuse stale identity.
## Task 1: kernel credentials and interpreter evidence
Files: create `packages/bus/src/peercred.mjs`, `packages/bus/src/peercred.py`,
`packages/bus/src/interpreter.mjs`; tests in `packages/bus/tests/peercred.test.mjs` and
`packages/bus/tests/interpreter.test.mjs`, with subprocess fixtures under `packages/bus/fixtures/`.
Interfaces: `inspectInterpreter()` returns a validated immutable record of path, version,
device, inode and sha256. `recordInterpreterStart(directory, record)` appends the actual startup
identity safely. `createPeerReader({interpreter})` returns `{read(socket),close()}`. read resolves
to `{pid,uid,gid}` from that exact socket, or refuses. Test-only injected OS/process operations
do not become product flags or environment overrides.
- [ ] Write interpreter safety, receipt replacement and cap-independent helper tests (T3/T10).
- [ ] Run each new test file alone and record the expected missing-module failures.
- [ ] Implement system interpreter validation and startup receipt append with no-follow, owner/type/mode checks.
- [ ] Write T2's eight-process barrier plus racing copier, wrong-fd case and post-helper socket exchange.
- [ ] Implement fd-3 helper, strict result validation, bounded async reader and helper shutdown.
- [ ] Run `node --test packages/bus/tests/interpreter.test.mjs` and separately `node --test packages/bus/tests/peercred.test.mjs`.
- [ ] Confirm process/socket cleanup, eight distinct kernel PIDs, copier refusal and original receipt preservation.
## Task 2: principal provenance and bus dispatch
Files: modify `packages/bus/src/{human,tester,server,runtime}.mjs`, create
`packages/bus/src/principal.mjs`, add `packages/bus/tests/principal.test.mjs`, and extend
`packages/bus/tests/human.test.mjs`. Extend server/runtime test files after inspecting their fixtures.
Interfaces: `verifyPrincipal(request, {peer,...trustedPathsAndProcessReaders})` returns
`{kind,business,id}`. Human maps to the business human, tester to tester, user to its verified
Console proof id. No roles or grants. Server obtains peer metadata from Task 1 only after
envelope validation and only for proofs. Existing cap dispatch never invokes the helper.
Future user binding stays refused without R3's trusted integration.
- [ ] Write T4/T5/T6/T8 process-table tests, including changed ancestry, distinct nonce namespaces and kind confusion.
- [ ] Run the focused new file and confirm expected failure before implementing the verifier.
- [ ] Parameterize the nonce/path portion of the existing human walk and retain all marker/binary/launch checks.
- [ ] Wire peer metadata, interpreter startup receipt and one-request cleanup into runtime/server.
- [ ] Test cap requests with a failing helper spy (M22), and test async refusal/close cleanup.
- [ ] Run each affected bus test file alone. Record any base fixture changes needed for real peer identity.
## Task 3: tester Unix HTTP and Console nonce spawning
Files: modify `packages/webui/src/{cli,serve,bus}.mjs`, create
`packages/webui/src/tester-socket.mjs` and `packages/cli/src/tester-http.mjs`; add focused
`packages/webui/tests/tester-socket.test.mjs` and `packages/cli/tests/tester-http.test.mjs`.
Extend existing WebUI transport tests.
Interfaces: `startTesterSocket({dataRoot,...handlerOptions})` owns the private listener and
cleanup. `testerRequest({dataRoot,method,path,body})` uses the fixed logical origin and bounded
Unix HTTP. Console human child spawning supplies its initial nonce, preserving agent markers.
R3 reuses that spawn pattern for its user-cli.
- [ ] Write T1/T3/T9 socket permission, unsafe-path, wrong-origin and instance separation tests.
- [ ] Confirm the new focused files fail, then implement --tester-socket and refusal of --tester-port.
- [ ] Keep tester principal, write allowlist and cookie/header refusals intact. Add no browser relay.
- [ ] Write and implement the initial-nonce Console spawn test and the helper request timeout test.
- [ ] Run each new or changed test file alone, verifying cleanup and no tester TCP bind.
## Task 4: release migration and operator evidence
Files: modify `scripts/{webui-service,stack-release}.sh`, inspect
`scripts/stack-copy.sh` closure, extend `packages/cli/tests/stack-release.test.mjs` and its
render fixtures. Update `docs/TOOLS.md` and package READMEs with the implementation.
Interfaces: `activate ID BUSINESS --tester-socket` explicitly replaces inherited tester-port
flags. Pointer and unit argv agree. Candidate support and interpreter preflight run before
any unit stop. Activation gate evidence records the same interpreter as the bus startup receipt.
- [ ] Write T7 explicit conversion, inherited mismatch and unsupported-target refusal fixtures.
- [ ] Run only the affected test file, then implement flag rendering, plan output and rollback disabling unsupported tester.
- [ ] Preserve old-copy recovery behavior and report restored old security state accurately.
- [ ] Add rehearsal scratch socket and T10 interpreter gate identity comparison.
- [ ] Verify same-copy activation refusal is preserved. Document N5's next-reviewed-copy re-pin path.
- [ ] Run the affected file alone, shell syntax checks, and inspect remaining tester-port references by purpose.
## Task 5: mutants, approved suites and manifest
Files: add the row's mutation runner and receipts under `agents/gander/work/queue-196/`.
Append BUILD-LOG phase receipts. No SESSIONS or queue file edits.
- [ ] Exercise M1-M23 against isolated scratch copies. Record killer test, restored hash and cleanup for each.
- [ ] Run focused tests on Node 24 and 26, including the concurrent T2 case and socket lifetime checks.
- [ ] Send Sage the scope and estimated duration, obtain the suite slot, then run approved bus/WebUI/CLI suites.
- [ ] Let Sage own the scripts/test-*.sh gate. Stop on the repository's two-failure rule.
- [ ] Build the exact changed-path manifest with acceptance evidence, search results, limits and consumer audit.
- [ ] Commit/push the verified candidate only after applicable suites pass. Send Duckworth and Sage path, commit and hashes for manifest review.
Execution stays with Gander in this worktree. No new agent delegation. Duckworth reviews
the manifest independently. Push is only to gander/row196. No merge, re-pin or deployment
is authorized by this plan.
@@ -0,0 +1,186 @@
{
"status": "uncommitted draft; regenerate after final tests",
"base": "4d26003f731a6eec439df6a48cacc3ef482a59c8",
"files": [
{
"path": "docs/TOOLS.md",
"baselineSha256": "5bd1bc697cd56e57b00e60c0e80be72cd1043a55a6d2fc0b9e2ccdd0c5cf9802",
"candidateSha256": "7eba3f1ac2e5a159c9c764f1c3e4f6fdfaf06d2904cdc36338723d5ddd9044c8"
},
{
"path": "packages/bus/README.md",
"baselineSha256": "4377cc32aed37fa1bb645c0bbc3340eed4ec01740e67327449949474e35d3fdb",
"candidateSha256": "becff52b647f28079d2756b26a7e46e28f14b695f57b2dede779e7cb43af3d96"
},
{
"path": "packages/bus/fixtures/peer-client.mjs",
"baselineSha256": null,
"candidateSha256": "60720461a7994011a9a30594715eb2c992be699cd61c656e77cbd609ad05328e"
},
{
"path": "packages/bus/src/human.mjs",
"baselineSha256": "b995fc5cff3af8ade4c990d0e3bebf9d83829e00f034ebc9c3dcfd05f252b573",
"candidateSha256": "0c6d96abe4607cba0b02b0638a2fc217ff8135bac9a47761a0ef015fbe9d3079"
},
{
"path": "packages/bus/src/interpreter.mjs",
"baselineSha256": null,
"candidateSha256": "d5d98a9133a1b449acc48143fd559d890a470c6ea210b30b9b70e491e6cd855a"
},
{
"path": "packages/bus/src/peercred.mjs",
"baselineSha256": null,
"candidateSha256": "29c88655e8b9aed8db604573c9213fd9f3c40ef35ff04f4eda8fbe8f1b8a32d7"
},
{
"path": "packages/bus/src/peercred.py",
"baselineSha256": null,
"candidateSha256": "d1536a8f472fa5b3773adb10555529ed049ad63b647c0b44c00ce5f25ce09f85"
},
{
"path": "packages/bus/src/principal.mjs",
"baselineSha256": null,
"candidateSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"path": "packages/bus/src/runtime.mjs",
"baselineSha256": "fe70f4b6568fbfec85e58d34f3cb8c3624daba65487d73ad24e981f2121cb27a",
"candidateSha256": "e001bdba127fc6e7a6e95ef1864e661b2842f3c797169ab0a6ccaa5e2cd7da00"
},
{
"path": "packages/bus/src/server.mjs",
"baselineSha256": "8a1b8eac60a5885bbfd36f939b63443de303ed298bf9e1ff653a5a7bcbae76d2",
"candidateSha256": "a5788c0a0b2b78de5eb2111a47c475e100cbb0e2eca4b601e7f027cac2c56d7e"
},
{
"path": "packages/bus/tests/channel-proof.test.mjs",
"baselineSha256": "98f12f509f0e292fa0b0e3924834f9d0f285c08ea5c4d22057566c49e2bd6c68",
"candidateSha256": "e99133c3c957a1e856e404343721add2430e4a0d8c75452517aef04942af56ab"
},
{
"path": "packages/bus/tests/interpreter.test.mjs",
"baselineSha256": null,
"candidateSha256": "d19779b97e80ed3d62f58d0e798df46c23f45fa02b14fb4ac7a2ff4690a734e2"
},
{
"path": "packages/bus/tests/peer-transport.test.mjs",
"baselineSha256": null,
"candidateSha256": "a39af877a2d9143cc8d0584af0d603ca7ab651db8787e5816579762f17295887"
},
{
"path": "packages/bus/tests/peercred.test.mjs",
"baselineSha256": null,
"candidateSha256": "2c4c0bed41e546ce94535d8c4547b1cf5552a181183525dd1d52e2af1d64ccb6"
},
{
"path": "packages/bus/tests/principal.test.mjs",
"baselineSha256": null,
"candidateSha256": "cefbc0688d823c69196d94bb71467884acd639070721286d7e8e4e986c8240c1"
},
{
"path": "packages/cli/README.md",
"baselineSha256": "2c9f8fe7143aa80e59826ab53bd046de2b5e0a8f3e81fe4e3df77726bb54d50b",
"candidateSha256": "23f4c767a67b358025f0d035d2a676e14e335478c480bc44a5c303066c40709c"
},
{
"path": "packages/cli/src/tester-http.mjs",
"baselineSha256": null,
"candidateSha256": "4bd7bdf0898844684ebf9444b110d2c5b6fa874b2681183e54f1f62f97823192"
},
{
"path": "packages/cli/tests/stack-release.test.mjs",
"baselineSha256": "b01ff656962c6fdb5bc3554cfbb13645b7f25adae5f6cd9608af8de6d6091c6c",
"candidateSha256": "4c92b7cc7c4b942a1bbb9a9e24c7c1ef10e23d3f9d166bee98ed1b7f6b8de4b0"
},
{
"path": "packages/cli/tests/tester-http.test.mjs",
"baselineSha256": null,
"candidateSha256": "fbaa36e64129aee324341364bde831c379c098b97932cceadb7fcaa7909d4b20"
},
{
"path": "packages/webui/README.md",
"baselineSha256": "78c7b60d23cf20863a1cb66e16af2f9363d08acc79c5b4a344cd8af4fbe5bd44",
"candidateSha256": "8bf41140a0c013a3d77757b66e442d46c278cf8eb0f54ac26b4e477e8265279b"
},
{
"path": "packages/webui/src/bus.mjs",
"baselineSha256": "76a1b57858c4aa41071746713a196299264851c18322a6f9869d553f0362d421",
"candidateSha256": "934ab2f9b6bde72aafe7814758550c283eb25a4f3a23b284021b13718d67178f"
},
{
"path": "packages/webui/src/cli.mjs",
"baselineSha256": "4f988333ec6af225a023323dda85a159b81a77f408a8dd7bff798fe9a1f4b24b",
"candidateSha256": "fbe9db9352300fb969aa889e40323c61b1a51d01f63434964be0a8c530240e50"
},
{
"path": "packages/webui/src/serve.mjs",
"baselineSha256": "ba7d3e35b225cb4e17a4d2530a0f34d0a3734641e3982229a38207a36b3cd433",
"candidateSha256": "894bdb260af418bef7ca9dac5dd19541ca898d6f946ed12fa832bc3475234709"
},
{
"path": "packages/webui/src/tester-socket.mjs",
"baselineSha256": null,
"candidateSha256": "1c1fe0c795adac97f28502564a75f26d5666d9be5ae51169f7dd5bb7741fad16"
},
{
"path": "packages/webui/tests/browser.mjs",
"baselineSha256": "1e70a0175f1d660f6b71449a1423ece83fbe8332108960fa9aff6f7a5e48022a",
"candidateSha256": "2f3ea92cbe1ca115301ee1e3b8fc55a206842606237446b700881ee57a1b18cb"
},
{
"path": "packages/webui/tests/proof-cli.test.mjs",
"baselineSha256": "c23975b140ed977f3b98dbbec5de289e9f930cc42089193c4aa7c106338a68ba",
"candidateSha256": "105cf4936db0498d0c0025698c7ee33ddce9530b43f6c829b77afdbcb3075207"
},
{
"path": "packages/webui/tests/proof.test.mjs",
"baselineSha256": "c06a1e673ef6f3df8f0473692c828aeba853369d2b668e821f1d41e6340b6503",
"candidateSha256": "5f34d699a1bce6f46f286def1a93fdd4d863d06bec6839677fae1041be7153dd"
},
{
"path": "packages/webui/tests/service.test.mjs",
"baselineSha256": "424be2837a18a41eab56b0deb576e35b460ac16b8c371762ad44896ffc3aad7d",
"candidateSha256": "2304ced2473c25b0369b4bb9b1433c1be3ee9ab8a6f75c971487abcd48cb1d1b"
},
{
"path": "packages/webui/tests/tester-browser-fixture.mjs",
"baselineSha256": null,
"candidateSha256": "9215ff92df774ba1e83819b87f758b53c8f3d19c2824c916490165914f1cb9e8"
},
{
"path": "packages/webui/tests/tester-browser.test.mjs",
"baselineSha256": "3cc6def0c8cfb6cf7bb91a52df6e57f18071549588b35394e0083f7dc938dacf",
"candidateSha256": "89ffc5cbed1630a523080e8561d3678c1f781fb657199a5cd6cdb8189f082c73"
},
{
"path": "packages/webui/tests/tester-cli.test.mjs",
"baselineSha256": "dd74517b336e1670a5910481920ba875ffb004d52d8c6bee7cc5efc81e43215d",
"candidateSha256": "a5c6c95dbda75dce37d38d3ce4761d3ad576babd9304a046e9a62b1b5dc2fdfd"
},
{
"path": "packages/webui/tests/tester-fixture.mjs",
"baselineSha256": "2169803c50815d214cab120eaa9d1542aa3b96bfe4738d68307eb0b92c1213f3",
"candidateSha256": "7d4f3a59d21675807b1bfb92d40b7378134df450590bc6d42fa4103ab1343348"
},
{
"path": "packages/webui/tests/tester-listener.test.mjs",
"baselineSha256": "aed4cec37401bc7953b1a872349ed77c70210fcfb75ebf6d4e881a393f92c7e9",
"candidateSha256": "7f0fc2daead11048cb86e0a74b6a223314785b1740a2927461d012b876a69f18"
},
{
"path": "packages/webui/tests/tester-socket.test.mjs",
"baselineSha256": null,
"candidateSha256": "c01a942da1ad66a08a4e7c92d3c7e64f517530acfe5517f1e9895767d5ea3c0b"
},
{
"path": "scripts/stack-release.sh",
"baselineSha256": "5bb3ac40102ce515f1acdcbc5670feb161ce40a193e94e2f8b104ebf98eddc38",
"candidateSha256": "59b39d63feb77f22abb4b7152c18feff53715cf3a10dde6374c69615329868c7"
},
{
"path": "scripts/webui-service.sh",
"baselineSha256": "d514caf757c03e5d02ef39b19359669bd2476dca1877f5d237c65bd62c33d2bf",
"candidateSha256": "67cc347231d4410acf0fd845bde58aeabf099d1d6cf3e36efda4db66c5438c10"
}
]
}
+22
View File
@@ -0,0 +1,22 @@
# Row 196 execution ledger
Plan: agents/gander/work/queue-196/PLAN.md
Base: 4d26003f7. Build authorized by sage-1371, mos-1021 item 2 and mos-1023 item 3.
Pre-flight: Task 1 supplies peer reader and interpreter evidence to Task 2 and release gate in Task 4. Task 2 supplies principal provenance; R3 supplies user policy. Task 3 supplies tester transport to Task 4. Interfaces match approved design.
Ruling: retain this repository packet as the ledger instead of adding a root .superpowers directory, honoring bootstrap-only root. No code commits before applicable suites pass; Duckworth is the independent manifest reviewer. Suites and mutant batches require Sage slot.
Task 1: in progress.
Task 1: interpreter tests RED missing module -> GREEN 3/3; peer reader RED missing module -> timeout diagnosed: Node spawn stdio pauses Socket -> explicit caller resume -> GREEN 3/3. Concurrency test observed nine distinct peers, eight accepted, copier refused, post-helper bidirectional traffic. No suite/commit yet.
Task 2: principal RED missing module -> GREEN 3/3; existing human 4/4, peer-transport RED -> GREEN 2/2, transport 5/5, tester 14/14, process 8/8, all individual files. Runtime records interpreter and supplies kernel peer to shared verifier; cap traffic does not spawn helper.
Task 3: BLOCKED by design contradiction, reported gander-0016/0017 at 2026-10-20T19:34:28Z. Node/libuv closes Unix listener by unlinking its pathname even after replacement inode. Scratch cleanup assertion reproduces; conditional JavaScript cleanup is too late. Tester module not wired into serve. Recommend explicitly accepting same-uid pathname replacement as residual under private-parent boundary. No product commit, suite or mutant batch.
Task 3 resumed: sage-1382 selected Duckworth option 1. Temporary bind -> rename -> conditional final inode cleanup implemented. Replacement survival and absence of temporary name RED -> GREEN. M24 is bind-final-directly. Remaining same-uid lstat/unlink race recorded in BUILD.md. No design weakening.
Gate run 1 bus: 463 pass, 1 fail, no other packages started. Failure was row164 byte baseline, which already records intentional re-pins in later rows. Ruling: update only human.mjs and webui bus.mjs pins because their approved row196 changes are intentional, preserving the other two pins, literal entry-path tests and plug-in import ban. This does not bypass a policy refusal or weaken the proof walk.
Gate run 2: bus 464/464 pass. WebUI stopped on second failure: tester-browser.test.mjs still constructed TCP URLs using Unix address().port. 269 top-level passing cases observed, 2 failed, final suite summary unavailable due interruption. CLI and M1-M24 not started. Not timing failures. Sage notified clear in gander-0023. Node24 peer 3/3 and principal 4/4 passed. No suite/mutant rerun without renewed slot. Next: migrate scratch browser fixture without adding a product relay.
Focused repair after gate stop: tester-browser 2/2 GREEN using CDP request interception and real scratch Unix HTTP, no TCP listener or product relay. Self-review added FIFO startup-receipt refusal (RED timeout -> GREEN) and helper file preflight (RED no refusal -> GREEN). interpreter 5/5 and peer 4/4. Renewed slot requested gander-0024; suites and mutant batch still stopped.
sage-1409 accepts the fixture repair and requires separate manifest items for the row164 hashes and startup refusals, each with tests and mutants. sage-1416 delays the slot about an hour; no suite or mutant batch before go. Draft MANIFEST.md and PRODUCT-FILES.json prepared. M25 FIFO, M26 helper preflight and M27 helper shutdown are prepared, not executed. Gander-0025 reports the planned M1-M27 batch.
Focused shutdown check: peer reader now awaits killed helper close events and server close awaits the reader. peer-shutdown-red.txt records the failing assertion, peercred-green.txt the 5/5 result. Final focused Node24 reruns: peercred 5/5, principal 4/4. Node26 peer-transport 3/3 after async reader close. git diff --check clean. These are single-file checks, not renewed suite acceptance.
Consumer audit found proof insertion missed the new socket flag, making pointer and rendered unit argument order disagree. Focused row196 release fixture extended with preview + explicit proof: RED exact-order mismatch -> GREEN 1/1 after adding --tester-socket to the insertion boundary. Existing proof-file no-read check passes. Evidence release-proof-order-{red,green}.txt. M28 prepared for that boundary. Final planned mutant batch M1-M28; none executed. Shell syntax and diff whitespace checks pass.
sage-1425: complete static self-review before slot, document refused caller outcome. Real Client test now verifies human-required for proof refusal and peercred-required for helper failure, without dispatch; peer-transport 4/4. Scratch HEAD-vs-candidate comparison returns human-required in both. Quackerjack's exact failing instance is not reproduced; client.mjs unchanged and abrupt close still outcome-unknown. Manifest states the limit.
Static review complete: peer/helper lifecycle, runtime, verifier walk, tester socket, Console spawn, Unix client, release/recovery/preflight and fixtures inspected. Found release path preflight missing before stop: long-path fixture RED -> GREEN with preflight; unsafe parent also refuses before any stop. release-path-{red,green}.txt. M29 prepared. Restored unchanged legacy tester-port-invalid wording. Prepared batch now M1-M29, still unexecuted. No further implementation changes planned before Sage's go.
sage-1464 (2026-10-20T20:40Z): renewed bus/WebUI/CLI and M1-M29 slot is fifth, after rows 192, 201, 204 and 200. Continue holding until Sage's go. Fleet notice #426(a), mos-1086, live 20:32:55Z: no branch deletion; any rebase must use a new branch name; leave pr-merge.sh -d off. No rebase or deletion is planned for this row.
sage-2051 / mos-1193 (2026-10-21T18:45Z) explicitly orders committing and fast-forward pushing the current row196 build before its deferred full verification, then rebasing onto a36523713 on new gander/row196-r2. This supersedes the earlier no-product-commit-before-suite gate for this preservation commit only. Full acceptance remains pending. No tests until Sage says clear. Subsequent batch order: M1-M29, bus, WebUI, CLI, every whole scripts/test-*.sh; TMPDIR must be created with mktemp under /mnt/storage/scratch/tmp, never /tmp. Preserve the old branch. Row252 design may proceed while waiting; no row252 code before Webby approves.
sage-2058 clear: clean detached 64539c96b worktree, short scratch TMPDIR, busy && before starts, pipefail and tee, mutation restoration hash and Git-status checks. M1-M22 killed; M23 rejected by broad runtime SyntaxError classification despite passing node --check, 3 pass/2 fail in killer. Stopped, restored clean, slot returned gander-0031 18:51:50Z. No suites started. sage-2063 calls stop 1; driver fix separately committed, no rerun before next clear. r2/REBASE.md records all ten conflict resolutions. Row252 design drafted, no code.
sage-2065 clear at 18:58:34Z: base killers 14/0, wall seconds interpreter 1.470, socket 1.528, peer 2.092, focused release 7.983. M23-M27 killed, no outer timeout. M28 survived because its generic first replacement now hits upstream sso_for_target, not the intended proof insertion. Stopped before M29 or any suite, restored every file and Git status clean; sweep found 0 leftover node/tee/Python readers. Slot handed back gander-0035 at 19:01:26Z. This is stop 2, awaiting Sage ruling; no mutation fix/rerun. r2/run2/SUMMARY.md and kill-table.json preserve counts and timings. Webby approved row252 design r1; nonblocking N1-N5 recorded in queue-252/BUILD-NOTES.md; code still waits on row196 landing and PRD amendment.
@@ -0,0 +1,10 @@
✔ channel conversion preserves the four proof/transport byte baselines and modules cannot import a proof caller (0.85226ms)
✔ channel recovery still requires the same shim path and rejects an absent or forged proof (308.49278ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 389.108211
@@ -0,0 +1,12 @@
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.430093ms)
✔ process reader gets own kernel identity without exposing environment values (0.499236ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.651712ms)
✔ real pid 1 remains inspectable when its environment is protected (0.268904ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 60.373844
@@ -0,0 +1,28 @@
✖ non-regular startup receipt refuses promptly without waiting on a FIFO peer (1008.229518ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1059.700302
✖ failing tests:
test at packages/bus/tests/interpreter.test.mjs:69:1
✖ non-regular startup receipt refuses promptly without waiting on a FIFO peer (1008.229518ms)
AssertionError [ERR_ASSERTION]: unsafe FIFO must refuse without blocking startup
null !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/interpreter.test.mjs:75:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: null,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,21 @@
✔ system Python is a resolved root-owned ELF, never a shim or home interpreter (1.874163ms)
✔ real system interpreter is available and safely inspected (16.54588ms)
✔ restart receipt appends actual replacement identity and refuses unsafe files (3.340596ms)
✖ activation gate compares fresh startup receipt to the current interpreter identity (1.116553ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.983973
✖ failing tests:
test at packages/bus/tests/interpreter.test.mjs:59:1
✖ activation gate compares fresh startup receipt to the current interpreter identity (1.116553ms)
TypeError: verifyInterpreterReceipt is not a function
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/interpreter.test.mjs:64:15)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,13 @@
✔ system Python is a resolved root-owned ELF, never a shim or home interpreter (1.992922ms)
✔ real system interpreter is available and safely inspected (18.869225ms)
✔ restart receipt appends actual replacement identity and refuses unsafe files (3.111417ms)
✔ activation gate compares fresh startup receipt to the current interpreter identity (1.357978ms)
✔ non-regular startup receipt refuses promptly without waiting on a FIFO peer (32.332597ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 105.511835
@@ -0,0 +1,35 @@
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/src/interpreter.mjs' imported from /home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/interpreter.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:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/src/interpreter.mjs'
}
Node.js v26.8.1
✖ packages/bus/tests/interpreter.test.mjs (29.295027ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 34.780279
✖ failing tests:
test at packages/bus/tests/interpreter.test.mjs:1:1
✖ packages/bus/tests/interpreter.test.mjs (29.295027ms)
'test failed'
@@ -0,0 +1,13 @@
✔ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (244.921933ms)
✔ helper output and failures fail closed including unsafe gid (2.053177ms)
✔ helper timeout, capacity and shutdown refuse without leaving children (26.359085ms)
✔ missing or non-regular peer helper refuses at construction before opening the bus (0.377385ms)
✔ shutdown waits for killed helper close before returning (5.980511ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 352.965488
@@ -0,0 +1,12 @@
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (2.18658ms)
✔ user requires its own nonce and full walk plus immediate same-tree Console parent (0.652569ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.382801ms)
✔ human leaf identity is reread after walking the parents (0.23585ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 69.614066
@@ -0,0 +1,6 @@
{
"version": "v24.22.0",
"path": "/mnt/storage/scratch/tmp/g196-node24-gzFqsW/bin/node",
"sha256": "cc2f601cee82d9a5a233302fe307abc600727035fd7d2c3a3571755989e95008",
"source": "https://nodejs.org/dist/v24.22.0/node-v24.22.0-linux-x64.tar.xz"
}
@@ -0,0 +1,27 @@
✖ missing or non-regular peer helper refuses at construction before opening the bus (0.992412ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 73.967525
✖ failing tests:
test at packages/bus/tests/peercred.test.mjs:93:1
✖ missing or non-regular peer helper refuses at construction before opening the bus (0.992412ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/peercred.test.mjs:94:9)
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: /peercred-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,28 @@
✖ shutdown waits for killed helper close before returning (1.761026ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 78.151245
✖ failing tests:
test at packages/bus/tests/peercred.test.mjs:98:1
✖ shutdown waits for killed helper close before returning (1.761026ms)
AssertionError [ERR_ASSERTION]: shutdown must await the helper close event
0 !== 1
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/peercred.test.mjs:106:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,12 @@
✔ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (7.225507ms)
✔ proof helper error and disconnect during verification never dispatch or bind (1.683542ms)
✔ Client receives explicit proof refusal codes without dispatch, not outcome-unknown (50.186199ms)
✔ asynchronous proof calls release their transient session on success, failure and timeout (28.028729ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 148.350042
@@ -0,0 +1,50 @@
✖ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (5.927716ms)
✖ proof helper error and disconnect during verification never dispatch or bind (1.959972ms)
ℹ tests 2
ℹ suites 0
ℹ pass 0
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 57.64878
✖ failing tests:
test at packages/bus/tests/peer-transport.test.mjs:9:1
✖ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (5.927716ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/peer-transport.test.mjs:15:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/bus/tests/peer-transport.test.mjs:20:1
✖ proof helper error and disconnect during verification never dispatch or bind (1.959972ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ undefined
- 'peercred-required'
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/peer-transport.test.mjs:25:9)
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: undefined,
expected: 'peercred-required',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,21 @@
✖ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (10044.708285ms)
ℹ started 9 clients
ℹ received 9 envelopes
ℹ read 9 peers
ℹ received 9 replies; after 0
✔ helper output and failures fail closed including unsafe gid (2.230205ms)
✔ helper timeout, capacity and shutdown refuse without leaving children (25.395463ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 1
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 10137.502565
✖ failing tests:
test at packages/bus/tests/peercred.test.mjs:15:1
✖ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (10044.708285ms)
'test timed out after 10000ms'
@@ -0,0 +1,13 @@
✔ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (274.013965ms)
✔ helper output and failures fail closed including unsafe gid (1.948532ms)
✔ helper timeout, capacity and shutdown refuse without leaving children (26.176111ms)
✔ missing or non-regular peer helper refuses at construction before opening the bus (0.331892ms)
✔ shutdown waits for killed helper close before returning (5.788665ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 396.110405
@@ -0,0 +1,35 @@
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/src/peercred.mjs' imported from /home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/peercred.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:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/src/peercred.mjs'
}
Node.js v26.8.1
✖ packages/bus/tests/peercred.test.mjs (31.978232ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 37.606365
✖ failing tests:
test at packages/bus/tests/peercred.test.mjs:1:1
✖ packages/bus/tests/peercred.test.mjs (31.978232ms)
'test failed'
@@ -0,0 +1,15 @@
✔ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (259.164726ms)
ℹ started 9 clients
ℹ received 9 envelopes
ℹ read 9 peers; states false/true/false,false/true/false,false/true/false,false/true/false,false/true/false,false/true/false,false/true/false,false/true/false,false/true/false
ℹ received 9 replies; after 9
✔ helper output and failures fail closed including unsafe gid (1.460068ms)
✔ helper timeout, capacity and shutdown refuse without leaving children (26.09001ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 355.364452
@@ -0,0 +1,12 @@
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.70707ms)
✔ user requires its own nonce and full walk plus immediate same-tree Console parent (0.585886ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.289109ms)
✔ human leaf identity is reread after walking the parents (0.170627ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53.605151
@@ -0,0 +1,35 @@
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/src/principal.mjs' imported from /home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/tests/principal.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:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/bus/src/principal.mjs'
}
Node.js v26.8.1
✖ packages/bus/tests/principal.test.mjs (28.063196ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 33.574982
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:1:1
✖ packages/bus/tests/principal.test.mjs (28.063196ms)
'test failed'
@@ -0,0 +1,16 @@
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (135.66144ms)
✔ startup token refusal returns safe code without value or partial listening broker (72.874581ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (105.550828ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (164.1127ms)
✔ trusted host registers later launches; socket clients never have a registration verb (109.156675ms)
✔ REQ-LIMIT-4 the broker process answers the launcher's mayLaunch op with the limits' awaited decision (100.062279ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (70.91343ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (105.059528ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 913.679719
@@ -0,0 +1,11 @@
✔ proof startup refuses partial flags non-preview and invalid fixture file metadata or bytes (1039.716731ms)
✔ serve argv carries only fixture proof path and header; startup explicitly reports proof on or off (241.707876ms)
✔ CLI applies proof on the main listener and rejects that header on its tester listener (232.216606ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1631.223267
@@ -0,0 +1,12 @@
✔ proxy proof admits named reads and sends, reads its fixture once, and never exposes the value (33.64167ms)
✔ proxy proof refuses missing wrong short or repeated proof before auth and before any read (9.334211ms)
✔ proxy proof header is refused on other Hosts and on the tester listener (4.637787ms)
✔ proof options reject unsafe or colliding header names without disclosing file contents (0.742812ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 170.24723
@@ -0,0 +1,2 @@
baseline human-required
candidate human-required
@@ -0,0 +1,9 @@
✔ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (5883.382797ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6639.610757
@@ -0,0 +1,36 @@
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (6495.905564ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 7217.097801
✖ failing tests:
test at packages/cli/tests/stack-release.test.mjs:2088:1
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (6495.905564ms)
AssertionError [ERR_ASSERTION]: stack-release: interpreter: {"path":"/usr/bin/python3","sha256":"fixture"}
stack-release: captured the rollback set: /mnt/storage/scratch/tmp/sr-eLxVuy/data-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/state/stack-rollback/20261020T201023Z-bb03a6447dc9
stack-release: stopped both units
stack-release: stopped store schema: 3g
stack-release: backup: /mnt/storage/scratch/tmp/sr-eLxVuy/data-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/backups/stack/20261020T201023Z-bb03a6447dc9
stack-release: installed bb03a6447dc98f172cbd28c2fbab126b37ff69c3
stack-release: gate passed on bb03a6447dc98f172cbd28c2fbab126b37ff69c3
stack-release: activate ok: bb03a6447dc98f172cbd28c2fbab126b37ff69c3 is live for acme
0 !== 3
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/stack-release.test.mjs:2108:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 3,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,9 @@
✔ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (5160.650984ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 6043.67883
@@ -0,0 +1,43 @@
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (3046.881963ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3691.652388
✖ failing tests:
test at packages/cli/tests/stack-release.test.mjs:2088:1
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (3046.881963ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines
[
'--port',
'43495',
'--business',
'acme',
...
'jason',
+ '--tester-socket',
'--proof-header',
'x-console-proof',
'--proof-file',
'/mnt/storage/scratch/tmp/sr-fLVpjD/proof.187',
- '--tester-socket'
]
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/stack-release.test.mjs:2102:9)
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: [ '--port', '43495', '--business', 'acme', '--public-host', 'console.example', '--auth-header', 'x-user', '--allow-user', 'jason', '--tester-socket', '--proof-header', 'x-console-proof', '--proof-file', '/mnt/storage/scratch/tmp/sr-fLVpjD/proof.187' ],
expected: [ '--port', '43495', '--business', 'acme', '--public-host', 'console.example', '--auth-header', 'x-user', '--allow-user', 'jason', '--proof-header', 'x-console-proof', '--proof-file', '/mnt/storage/scratch/tmp/sr-fLVpjD/proof.187', '--tester-socket' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,9 @@
✔ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (4452.603661ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5183.817431
@@ -0,0 +1,36 @@
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (2938.271119ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3279.087225
✖ failing tests:
test at packages/cli/tests/stack-release.test.mjs:2088:1
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (2938.271119ms)
AssertionError [ERR_ASSERTION]: stack-release: captured the rollback set: /mnt/storage/scratch/tmp/sr-QgMOBP/data/state/stack-rollback/20261020T194438Z-6e9ce64cf63b
stack-release: drop --tester-port 7333; the target Console does not support it
stack-release: stopped both units
stack-release: stopped store schema: 3g
stack-release: backup: /mnt/storage/scratch/tmp/sr-QgMOBP/data/backups/stack/20261020T194438Z-6e9ce64cf63b
stack-release: installed 6e9ce64cf63b125d007476844c4d695a308514d8
stack-release: gate passed on 6e9ce64cf63b125d007476844c4d695a308514d8
stack-release: activate ok: 6e9ce64cf63b125d007476844c4d695a308514d8 is live for acme
0 !== 3
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/cli/tests/stack-release.test.mjs:2095:45)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 3,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,9 @@
✔ row196 socket flag renders only for supporting targets and refuses mixed transports (16.910871ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 77.189844
@@ -0,0 +1,32 @@
✖ row196 socket flag renders only for supporting targets and refuses mixed transports (7.489228ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 75.646616
✖ failing tests:
test at packages/webui/tests/service.test.mjs:812:1
✖ row196 socket flag renders only for supporting targets and refuses mixed transports (7.489228ms)
AssertionError [ERR_ASSERTION]: usage: scripts/webui-service.sh copy COMMIT [--copies DIR] [--allow-worktree] | render [--copy ID [--copies DIR]] [FLAGS] | install --copy ID [--copies DIR] [--dir DIR] [--no-reload] [--allow-worktree] [FLAGS] | uninstall [--dir DIR] [--no-reload] | status [--dir DIR]
FLAGS: [--port N] [--business ID] [--board URL] [--public-host NAME --auth-header HEADER --allow-user ID [--send]] [--tester-port N] [--proof-header NAME --proof-file PATH]
--tester-port: 1024..65535, different from --port and never 7332; loopback tester listener.
--allow-worktree is for the tests only: copy and install otherwise refuse outside the canonical checkout.
4 !== 0
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/webui/tests/service.test.mjs:815:9)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 4,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,40 @@
✔ tester service render carries the tester port alongside existing flags (24.94035ms)
✔ tester service refuses forwarded 7332 even with another main port (9.82912ms)
✔ tester service invalid ports refuse render and install with exit 4 and leave the unit alone (104.005996ms)
✔ tester service counts the digits as written before it strips leading zeros (10.30812ms)
✔ tester service install and status show the tester port without changing send reporting (205.553924ms)
✔ render prints the unit without the preview flags (7.561049ms)
✔ render carries every serve flag, the preview set included (6.973808ms)
✔ a partial preview set is refused (16.637515ms)
✔ a value the unit cannot carry is refused, for every flag (256.274405ms)
✔ the rendered unit names none of the human proof agent markers and sets no agent variable (16.730737ms)
✔ install into --dir with --no-reload writes the file whole, then reports unchanged (48.962613ms)
✔ install without --no-reload runs daemon-reload and nothing that starts the unit (20.812535ms)
✔ uninstall refuses while the unit is running, with or without --no-reload, and removes it once it is not (109.285453ms)
✔ status prints the unit state and GET /healthz on the installed port (162.506277ms)
✔ usage: an unknown verb or flag, and start or enable, exit 4 (59.94317ms)
✔ without --send, render is the template with its placeholders filled, byte for byte (32.400878ms)
✔ render --send with the preview set ends ExecStart with --send and changes nothing else (52.094309ms)
✔ --send without the whole preview set is refused and writes nothing (131.187356ms)
✔ install --send into --dir with --no-reload writes the sending unit, and status says whether it sends (215.263849ms)
✔ uninstall and status take no --send (29.745082ms)
✔ install refuses while the unit is running, in any state but inactive or failed, and changes nothing (248.230583ms)
✔ when the state read fails, install and uninstall refuse with one line and change nothing (71.067475ms)
✔ install needs --copy naming a copy that exists, and refuses outside the canonical checkout without --allow-worktree (63.261377ms)
✔ install refuses unless the bus unit runs from the same copy, naming both paths, and changes nothing (235.658605ms)
✔ render --copy runs the copy and reads the queue in the checkout; without --copy the unit is unchanged (19.314052ms)
✔ the uid header takes only an Authentik uid; the username header still takes a username (300.432846ms)
✔ a preview value cannot start with - or . (28.388974ms)
✔ status names the copy and whether refactor has moved past it, and says when the unit runs from no copy (388.565391ms)
✔ proxy proof render carries both flags but never opens the fixture file (10.105723ms)
✔ proxy proof render refuses incomplete non-preview missing unsafe-mode and repo/copy paths (80.016026ms)
✔ proxy proof status retains send and tester reporting with both proof flags (97.518111ms)
✔ row196 socket flag renders only for supporting targets and refuses mixed transports (18.052409ms)
ℹ tests 32
ℹ suites 0
ℹ pass 32
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3155.23567
@@ -0,0 +1,18 @@
{
"bus": {
"top_level_passes_observed": 464,
"top_level_failures": 0,
"final_pass_count": [
"464"
],
"interrupted": false
},
"webui": {
"top_level_passes_observed": 269,
"top_level_failures": 2,
"final_pass_count": [],
"interrupted": true
},
"mutants_executed": 0,
"cli_started": false
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,83 @@
[
{
"package": "bus",
"args": [
"--test",
"--test-reporter=tap",
"--test-concurrency=2",
"packages/bus/tests/account-migration.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/comment-migration.test.mjs",
"packages/bus/tests/credential-status.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/hard-waits.test.mjs",
"packages/bus/tests/human.test.mjs",
"packages/bus/tests/instances-verb.test.mjs",
"packages/bus/tests/interpreter.test.mjs",
"packages/bus/tests/land.test.mjs",
"packages/bus/tests/launch-notice.test.mjs",
"packages/bus/tests/limit-checks.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/name-migration.test.mjs",
"packages/bus/tests/names.test.mjs",
"packages/bus/tests/peer-transport.test.mjs",
"packages/bus/tests/peercred.test.mjs",
"packages/bus/tests/plugins-verb.test.mjs",
"packages/bus/tests/principal.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/project-runtime.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/schema.test.mjs",
"packages/bus/tests/single-use.test.mjs",
"packages/bus/tests/spec-migration.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/store.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-migration.test.mjs",
"packages/bus/tests/usage-runtime.test.mjs",
"packages/bus/tests/usage.test.mjs",
"packages/bus/tests/web.test.mjs",
"packages/bus/tests/workflow-roles.test.mjs"
],
"log": "/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/agents/gander/work/queue-196/evidence/suite-bus.tap",
"failures": 1,
"stopped": false,
"code": 1,
"signal": null
}
]
@@ -0,0 +1,143 @@
[
{
"package": "bus",
"args": [
"--test",
"--test-reporter=tap",
"--test-concurrency=2",
"packages/bus/tests/account-migration.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/comment-migration.test.mjs",
"packages/bus/tests/credential-status.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/hard-waits.test.mjs",
"packages/bus/tests/human.test.mjs",
"packages/bus/tests/instances-verb.test.mjs",
"packages/bus/tests/interpreter.test.mjs",
"packages/bus/tests/land.test.mjs",
"packages/bus/tests/launch-notice.test.mjs",
"packages/bus/tests/limit-checks.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/name-migration.test.mjs",
"packages/bus/tests/names.test.mjs",
"packages/bus/tests/peer-transport.test.mjs",
"packages/bus/tests/peercred.test.mjs",
"packages/bus/tests/plugins-verb.test.mjs",
"packages/bus/tests/principal.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/project-runtime.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/schema.test.mjs",
"packages/bus/tests/single-use.test.mjs",
"packages/bus/tests/spec-migration.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/store.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-migration.test.mjs",
"packages/bus/tests/usage-runtime.test.mjs",
"packages/bus/tests/usage.test.mjs",
"packages/bus/tests/web.test.mjs",
"packages/bus/tests/workflow-roles.test.mjs"
],
"log": "/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/agents/gander/work/queue-196/evidence/suite-bus.tap",
"failures": 0,
"stopped": false,
"code": 0,
"signal": null
},
{
"package": "webui",
"args": [
"--test",
"--test-reporter=tap",
"--test-concurrency=2",
"packages/webui/tests/assistant.test.mjs",
"packages/webui/tests/board-held.test.mjs",
"packages/webui/tests/browser-edge.test.mjs",
"packages/webui/tests/browser.test.mjs",
"packages/webui/tests/bus-browser.test.mjs",
"packages/webui/tests/bus.test.mjs",
"packages/webui/tests/channel-chat.test.mjs",
"packages/webui/tests/chat-browser.test.mjs",
"packages/webui/tests/chat.test.mjs",
"packages/webui/tests/conversation.test.mjs",
"packages/webui/tests/discord.test.mjs",
"packages/webui/tests/docs-browser.test.mjs",
"packages/webui/tests/editor-browser.test.mjs",
"packages/webui/tests/editor-provenance.test.mjs",
"packages/webui/tests/editor.test.mjs",
"packages/webui/tests/history-return-flow.test.mjs",
"packages/webui/tests/instances-browser.test.mjs",
"packages/webui/tests/instances.test.mjs",
"packages/webui/tests/limits-browser.test.mjs",
"packages/webui/tests/limits-route.test.mjs",
"packages/webui/tests/markdown-preview.test.mjs",
"packages/webui/tests/messages.test.mjs",
"packages/webui/tests/pinned-copy.test.mjs",
"packages/webui/tests/preview.test.mjs",
"packages/webui/tests/project-file.test.mjs",
"packages/webui/tests/project-name-repo.test.mjs",
"packages/webui/tests/proof-cli.test.mjs",
"packages/webui/tests/proof.test.mjs",
"packages/webui/tests/reads.test.mjs",
"packages/webui/tests/relaunch.test.mjs",
"packages/webui/tests/return-flow.test.mjs",
"packages/webui/tests/routes-browser.test.mjs",
"packages/webui/tests/routes.test.mjs",
"packages/webui/tests/send-browser.test.mjs",
"packages/webui/tests/send.test.mjs",
"packages/webui/tests/serve.test.mjs",
"packages/webui/tests/service.test.mjs",
"packages/webui/tests/settings-form.test.mjs",
"packages/webui/tests/settings-write.test.mjs",
"packages/webui/tests/shell.test.mjs",
"packages/webui/tests/tester-browser.test.mjs",
"packages/webui/tests/tester-cli.test.mjs",
"packages/webui/tests/tester-listener.test.mjs",
"packages/webui/tests/tester-socket.test.mjs",
"packages/webui/tests/usage-browser.test.mjs",
"packages/webui/tests/usage.test.mjs",
"packages/webui/tests/views.test.mjs"
],
"log": "/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/agents/gander/work/queue-196/evidence/suite-webui.tap",
"failures": 2,
"stopped": true,
"code": 1,
"signal": null
}
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
✔ tester Console banner exposes only instance route and name editors and saves a route as tester (903.47494ms)
✔ row 177: the tester Console chat composer is enabled and a send shows in its own thread (779.82198ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1835.567916
@@ -0,0 +1,29 @@
✖ tester cleanup must leave a replacement inode untouched (4.588498ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 77.335255
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:31:1
✖ tester cleanup must leave a replacement inode untouched (4.588498ms)
AssertionError [ERR_ASSERTION]: closing the listener must not unlink a replacement inode
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/webui/tests/tester-socket.test.mjs:40:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,9 @@
✔ tester cleanup must leave a replacement inode untouched (4.721014ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 69.761924
@@ -0,0 +1,29 @@
✖ tester cleanup must leave a replacement inode untouched (5.181827ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 70.292577
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:31:1
✖ tester cleanup must leave a replacement inode untouched (5.181827ms)
AssertionError [ERR_ASSERTION]: closing the listener must not unlink a replacement inode
false !== true
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/webui/tests/tester-socket.test.mjs:41:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,10 @@
✔ tester-port validates range tunnel main port and requires configured bus before listening (842.750345ms)
✔ CLI tester listener binds a private Unix socket and spawns TESTER_CLI for every Console bus read (2447.891251ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3426.404641
@@ -0,0 +1,10 @@
✔ tester HTTP helper uses instance socket, logical origin and JSON without retrying (16.169258ms)
✔ tester HTTP timeout closes the socket and does not retry (24.057725ms)
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 109.82809
@@ -0,0 +1,18 @@
✔ main listener refuses x-mosaic-tester before any board or bus read for every Host (17.594021ms)
✔ tester listener rejects preview binding and requires its fixed logical Host and no proxy auth header (97.026908ms)
✔ tester listener writes only instances routes and names with tester receipts and actor (102.526377ms)
✔ tester listener refuses every human-only write with no bus call (74.180878ms)
✔ tester-off and tester-required reads return HTTP 403 (72.172231ms)
✔ tester credentials routes and committed project reads keep secrets and paths bounded (139.034531ms)
✔ row 177 W1: the tester chat makes, sends to, opens and reads its own thread as tester (19.236645ms)
✔ row 177 W2: tester chat writes refuse a missing or foreign Origin, cross-site, text/plain and the proxy header with no bus call (9.497407ms)
✔ row 177 W3: the human thread through the tester chat is thread-not-owned, and bus.tester off answers tester-off (22.342419ms)
✔ row 177 W4: the main listener refuses x-mosaic-tester on chat reads and writes before the chat (1.907ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 677.96388
@@ -0,0 +1,11 @@
✔ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (56.084397ms)
✔ private tester refuses symlink, unsafe parent and long path without repairs (0.822118ms)
✔ tester cleanup must leave a replacement inode untouched (1.433318ms)
ℹ tests 3
ℹ suites 0
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 119.311098
@@ -0,0 +1,35 @@
node:internal/modules/esm/resolve:272
throw new ERR_MODULE_NOT_FOUND(
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/webui/src/tester-socket.mjs' imported from /home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/webui/tests/tester-socket.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:///home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/packages/webui/src/tester-socket.mjs'
}
Node.js v26.8.1
✖ packages/webui/tests/tester-socket.test.mjs (33.788429ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 42.005573
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:1:1
✖ packages/webui/tests/tester-socket.test.mjs (33.788429ms)
'test failed'
@@ -0,0 +1,22 @@
✔ the tester binds only when the business sets bus.tester to exactly true (61.599223ms)
✔ a business may not name a role tester, so the tester address is never a role (17.534761ms)
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (37.114694ms)
✔ a tester message is from tester, needs no prefix, and writes no human.input or authority (14.658214ms)
✔ a tester message to a role no run holds refuses tester-target-not-running and stores nothing (10.010587ms)
✔ a tester message is refused while the run is only launched, and routes once it claims (11.801045ms)
✔ a human message to a role no run holds still waits and tells launch.by (9.843013ms)
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (21.065845ms)
✔ the tester reads the human views a reader reads (23.085923ms)
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.132907ms)
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.338746ms)
✔ testerNonce reads only the tester's variable and gives null on any read error (0.167036ms)
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (82.495712ms)
✔ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (949.011498ms)
ℹ tests 14
ℹ suites 0
ℹ pass 14
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1344.0805
@@ -0,0 +1,13 @@
✔ socket capability stamps launch identity; shared views use wire, no SQL client (55.397456ms)
✔ two wire claims serialize; a lost reply never automatically retries (48.315452ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (34.817302ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.903844ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (29.646174ms)
ℹ tests 5
ℹ suites 0
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 288.566303
@@ -0,0 +1,18 @@
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (67.351635ms)
✔ decisions route serves the roles and recent lists from the reader view (32.001579ms)
✔ credentials route serves the broker credential status from the reader view (11.335934ms)
✔ replies route serves the replies to one message from the reader view (33.381558ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (56.18236ms)
✔ bus refusals map to statuses and never carry a path or a stack (26.580535ms)
✔ humanCall speaks the human transport protocol without blocking the server (1664.225925ms)
✔ busReader takes --business, else the live bus host, else refuses (179.394177ms)
✔ humanCall kills a transport that runs past its timeout (527.236263ms)
✔ serve refuses a --business value that is not a business id (267.848133ms)
ℹ tests 10
ℹ suites 0
ℹ pass 10
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3002.288149
@@ -0,0 +1,16 @@
// Synchronous mutation preparation only. No file is written until every replacement is unique.
export function guardMutation(input, mutate, { auditOnly = false } = {}) {
const targets = [];
class UniqueText extends String {
replace(target, replacement) { return this.applyReplacement('replace', target, replacement); }
replaceAll(target, replacement) { return this.applyReplacement('replaceAll', target, replacement); }
applyReplacement(method, target, replacement) {
if (typeof target !== 'string' || !target) throw Error('mutation target must be a nonempty string');
const source = String(this), count = source.split(target).length - 1;
targets.push({ method, target, count });
if (count !== 1 && !auditOnly) throw Error(`mutation target occurs ${count} times, expected exactly once: ${target}`);
return new UniqueText(String.prototype[method].call(source, target, replacement));
}
}
return { text: String(mutate(new UniqueText(input))), targets };
}
+72
View File
@@ -0,0 +1,72 @@
import { guardMutation } from './mutant-guard.mjs';
// Authorized batch only. Mutate isolated scratch copies, never the candidate or installed tree.
import {mkdtempSync,cpSync,readFileSync,writeFileSync,rmSync,symlinkSync,existsSync,mkdirSync} from 'node:fs';
import {tmpdir} from 'node:os';import {join,resolve} from 'node:path';
import {spawnSync,execFileSync} from 'node:child_process';import {createHash} from 'node:crypto';
import {fileURLToPath} from 'node:url';
const root=fileURLToPath(new URL('../../../../',import.meta.url));
const out=join(root,'agents/gander/work/queue-196/evidence/mutants');mkdirSync(out,{recursive:true});
const p='packages/bus/src/principal.mjs',h='packages/bus/src/human.mjs',w='packages/webui/src/tester-socket.mjs',s='packages/bus/src/server.mjs',peer='packages/bus/src/peercred.mjs';
const principal='packages/bus/tests/principal.test.mjs',socket='packages/webui/tests/tester-socket.test.mjs',transport='packages/bus/tests/peer-transport.test.mjs',interpreter='packages/bus/tests/interpreter.test.mjs';
const release=['--test-name-pattern=row196 explicit socket','packages/cli/tests/stack-release.test.mjs'];
const specs=[
['M1', 'packages/webui/src/serve.mjs','tester uses TCP',socket, text=>text.replace('if (tester) return bindTesterSocket(server, testerSocket);',"if (tester) return new Promise(r=>server.listen(0,'127.0.0.1',()=>r(server)));" )],
['M2',w,'socket mode widened',socket,t=>t.replace('chmodSync(temporary, 0o600)','chmodSync(temporary, 0o666)')],
['M3','packages/webui/src/serve.mjs','sign-in accepted on tester',socket,t=>t.replace("if (tester && (Object.hasOwn(req.headers, 'authorization')", "if (false && (Object.hasOwn(req.headers, 'authorization')")],
['M4',p,'copied PID accepted',principal,t=>t.replace('peer.pid !== proof.pid','false')],
['M5',peer,'helper parse error ignored','packages/bus/tests/peercred.test.mjs',t=>t.replace('finish(false, Object.freeze(p));\n } catch { finish(true); }','finish(false, Object.freeze(p));\n } catch { finish(false, {pid:2,uid:0,gid:0}); }')],
['M6',p,'foreign uid accepted',principal,t=>t.replace('peer.uid !== process.getuid()','false')],
['M7',w,'unsafe parent accepted',socket,t=>t.replace('(dir.mode & 0o077)','false')],
['M8',h,'agent environment walk skipped',principal,t=>t.replace('current.env !== null && MARKERS.some','false && MARKERS.some')],
['M9',h,'leaf identity not reread',principal,t=>t.replace('if (again.startTime !== proof.startTime || again.ppid !== p.ppid)','if (false)')],
['M10',p,'Console direct-parent check removed',principal,t=>t.replace("if (parent.uid !== peer.uid || parent.argv[1] !== resolve(paths.console) || parent.argv[2] !== 'serve')",'if (false)')],
['M11',p,'Console basename accepted',principal,t=>t.replace("import { resolve }", "import { resolve, basename }").replace('parent.argv[1] !== resolve(paths.console)','basename(parent.argv[1] ?? "") !== basename(paths.console)')],
['M12',h,'unknown proof identity accepted',principal,t=>t.replace("Object.keys(proof).some((k) => !['pid', 'startTime', 'nonce', 'business'].includes(k))",'false')],
['M13',s,'transient cap leaked',transport,t=>t.replace('broker.disconnect(boundCap)','void(boundCap)')],
['M14',p,'tester walk used for user',principal,t=>t.replace("verifyHuman(checked, { cliPath: paths[kind], launches, readProcess,\n nonceKey: kind === 'user' ? 'MOSAIC_BUS_USER_NONCE' : 'MOSAIC_BUS_CLI_NONCE' })",'verifyTester(checked, {cliPath:paths[kind],launches,readProcess,readNonce:()=>checked.nonce})')],
['M15','scripts/stack-release.sh','TCP retained during conversion',release,t=>t.replace('RUN_CONSOLE=("${without_tester[@]}" --tester-socket)','RUN_CONSOLE=("${BASE_CONSOLE[@]}" --tester-socket)')],
['M16','scripts/stack-release.sh','TCP reopened on downgrade',release,t=>t.replace('RUN_CONSOLE=("${without_tester[@]}") DROP_TESTER="--tester-socket','RUN_CONSOLE=("${without_tester[@]}" --tester-port 7333) DROP_TESTER="--tester-socket')],
['M17','packages/bus/src/broker.mjs','tester switch ignored','packages/bus/tests/tester.test.mjs',t=>t.replace("if (this.#business(record.business).vars?.['bus.tester'] !== true)",'if (false)')],
['M18',p,'user kind becomes owner',principal,t=>t.replace("return { kind: 'user', business, id: proof.user }","return { kind: 'owner', business, id: proof.user }")],
['M19','packages/cli/src/tester-http.mjs','selected instance root ignored',socket,t=>t.replace('socketPath: testerSocketPath(dataRoot)','socketPath: testerSocketPath(dataRoot + "/wrong-instance")')],
['M20',peer,'helper gets another pending socket','packages/bus/tests/peercred.test.mjs',t=>t.replace('export function createPeerReader','function originalPeerReader')+`\nexport function createPeerReader(options){const base=originalPeerReader(options),queue=[];let scheduled=false;return{read(socket){return new Promise((resolve,reject)=>{queue.push({socket,resolve,reject});if(!scheduled){scheduled=true;queueMicrotask(()=>{scheduled=false;const jobs=queue.splice(0);jobs.forEach((job,i)=>base.read(jobs[(i+1)%jobs.length].socket).then(job.resolve,job.reject));});}});},close(){base.close();}};}\n`],
['M21','packages/bus/src/interpreter.mjs','user-owned interpreter accepted',interpreter,t=>t.replace('s.uid !== 0 || (s.mode & 0o022)','false || (s.mode & 0o022)')],
['M22',s,'cap requests invoke helper',transport,t=>t.replace(' if (kind) {',' if (r.cap) await peers.read(socket);\n if (kind) {')],
['M23','packages/bus/src/interpreter.mjs','restart retains stale identity',interpreter,t=>t.replace('export function recordInterpreterStart(directory, record) {','export function recordInterpreterStart(directory, record) {\n try { readFileSync(join(directory,"peercred-starts.jsonl")); return; } catch {}')],
['M24',w,'listener binds final path directly',socket,t=>t.replace("const temporary = join(parent, '.' + randomBytes(4).toString('hex') + '.s');",'const temporary = path;')],
['M25','packages/bus/src/interpreter.mjs','FIFO receipt blocks before type refusal',['--test-name-pattern=non-regular startup receipt',interpreter],t=>t.replace('constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600','constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600')],
['M26',peer,'missing helper accepted at startup',['--test-name-pattern=missing or non-regular peer helper','packages/bus/tests/peercred.test.mjs'],t=>t.replace('if (!s.isFile() || s.isSymbolicLink()) throw Error();','if (false) throw Error();').replace('catch { throw refused(); }','catch {}')],
['M27',peer,'shutdown returns before helpers are reaped',['--test-name-pattern=shutdown waits','packages/bus/tests/peercred.test.mjs'],t=>t.replace(" await Promise.all([...children].map(child => new Promise(resolve => {\n child.once('close', resolve);\n child.kill('SIGKILL');\n })));"," for (const child of children) child.kill('SIGKILL');")],
['M28','scripts/stack-release.sh','proof pair placed after tester socket flag',release,t=>t.replace('case "${without_proof[i]}" in --send | --tester-port | --tester-socket) break','case "${without_proof[i]}" in --send | --tester-port) break')],
['M28b','scripts/stack-release.sh','SSO placed after tester socket flag',['--test-name-pattern=row196 SSO socket','packages/cli/tests/stack-release.test.mjs'],t=>t.replace('case "${kept[i]}" in --send | --tester-port | --tester-socket) break','case "${kept[i]}" in --send | --tester-port) break')],
['M29','scripts/stack-release.sh','unsafe tester path reaches service stop',release,t=>t.replace(t.split('\n').find(line=>line.includes('|| check_refuse tester-socket-path'))+'\n','')],
];
const hash=x=>createHash('sha256').update(x).digest('hex');
const results=[];
for (const [id,file,,,mutate] of specs) {
try { guardMutation(readFileSync(join(root,file),'utf8'),mutate); }
catch(error) { throw Error(id+': '+error.message); }
}
const scratch=mkdtempSync(join(tmpdir(),'g196-mut-'));
try {
for(const name of ['packages','scripts','adapters','roles','contracts','RELEASE','package.json','package-lock.json','.git'])if(existsSync(join(root,name)))cpSync(join(root,name),join(scratch,name),{recursive:true,filter:src=>!src.includes('/node_modules/')&&!src.endsWith('/node_modules')});
symlinkSync(join(root,'node_modules'),join(scratch,'node_modules'));
for(const [id,file,description,test,mutate] of specs) {
const at=join(scratch,file),original=readFileSync(at,'utf8'),before=hash(original),changed=guardMutation(original,mutate).text;
if(changed===original)throw Error(id+' did not change bytes');
writeFileSync(at,changed);
const syntax=spawnSync(file.endsWith('.sh')?'bash':process.execPath,[file.endsWith('.sh')?'-n':'--check',at],{encoding:'utf8'});
if(syntax.status!==0)throw Error(id+' invalid mutation: '+syntax.stderr);
const args=['--test',...(Array.isArray(test)?test:[test])];
const run=spawnSync(process.execPath,args,{cwd:scratch,encoding:'utf8',timeout:30000,maxBuffer:2*1024*1024});
const log=(run.stdout??'')+(run.stderr??'');writeFileSync(join(out,id+'.txt'),log);
writeFileSync(at,original);if(hash(readFileSync(at))!==before)throw Error(id+' restore hash failed');
const failed=/[ℹ#]\s*(?:fail|tests failed)\s+[1-9]/.test(log) || /✖/.test(log);
// Syntax validity is decided exclusively by the node --check preflight above.
const killed=run.status!==0&&failed;
const result={id,description,test,args,status:run.status,signal:run.signal,killed,restoredSha256:before};results.push(result);
writeFileSync(join(out,'results.json'),JSON.stringify(results,null,2)+'\n');
console.log(id+' '+(killed?'KILLED':'SURVIVED/INVALID'));
if(!killed) {process.exitCode=1;break;}
}
} finally {rmSync(scratch,{recursive:true,force:true});}
+55
View File
@@ -0,0 +1,55 @@
# Row 196 r2 rebase and gate record
Preservation: caf5c9b71b7ffe3e1a195c50a96439cb1d94bb0f, fast-forward pushed
on gander/row196 per sage-2051 / mos-1193. Stat against 4d26003f7:
93 files changed, 9940 insertions(+), 178 deletions(-).
Rebased candidate: 64539c96bb9eb34ff0cce6ab2e04410041a2a31e, on a36523713,
pushed as new gander/row196-r2. Old branch preserved. No force push or deletion.
range-diff.txt and patch-id-{before,after}.txt hold the exact comparison.
Stable patch ids differ: 294d45c6c8494e83e4276a8c554fcbe85674f3d0 before;
fb96c71db3715d7ea766b278377c4f215ba1537b after.
## Merge reasoning for the ten conflicted files
| File | Resolution and reason |
|---|---|
| BUILD-LOG.md | Retain upstream append-only phases followed by row196 entries. No historical entry removed or rewritten. |
| docs/TOOLS.md | Combine socket/legacy tester flags with upstream --sso and --no-send usage. Keep new operator modes. |
| packages/bus/src/runtime.mjs | Keep upstream host executor imports, run-directory sweep, grant reconciliation and host API. Add interpreter receipt and peer reader before listen, shared verifier and per-request binds. No second verifier. |
| packages/bus/src/server.mjs | Keep upstream HOST_VERBS/HOSTS_VERBS and host argument. Merge user proof and peer checks, with idempotent transient-cap release on host async completion too, avoiding duplicate disconnect. |
| packages/cli/tests/stack-release.test.mjs | Keep upstream extracted fixture import and all new upstream tests, append the whole row196 case outside upstream's closing block. Move row196 optional long data-root parameter into stack-release-fixture.mjs, where mk now lives. |
| packages/webui/src/cli.mjs | Retain SSO parsing, startup, summary and shutdown; replace only tester TCP with private Unix transport. Preserve testerNoSignIn when SSO is on. |
| packages/webui/src/serve.mjs | Retain SSO session/Host checks and signer. Add socket-only tester transport and fixed logical origin. Tester refuses authorization, session/login cookies and provider headers; it never uses the SSO signer. |
| packages/webui/tests/proof-cli.test.mjs | Retain upstream lane imports while moving the tester assertion to Unix HTTP. Close the fixture's socket before spawning the same-root CLI. No product relay. |
| scripts/stack-release.sh | Retain external-unit protocol, instance flags, SSO transformation, no-send and grant/config checks. Run socket migration on BASE_CONSOLE after sso_base. Preserve interpreter gate after external start-floor calculation. New socket flag participates in proof/SSO insertion and rollback carry, avoiding ordering and TCP-fallback regressions. External callers cannot use the new render-changing flag. |
| scripts/webui-service.sh | Retain instance selection, PORT_SET and SSO flag/status handling; add socket support checks and mode conflict. Status regex recognizes either legacy tester port or socket after send. |
Follow-through outside the conflict blocks: migrate the new SSO tester fixture to
Unix HTTP while retaining every sign-in refusal; adapt M15 to BASE_CONSOLE; retain
approved DESIGN.md/PLAN.md, absent on the new base. No tests before Sage's clear.
The two reviewed row164 file hashes remain unchanged by this rebase.
## First r2 slot, sage-2058
Clean detached worktree pinned to 64539c96b, linked node_modules. paths.txt
records it and the short mktemp TMPDIR under /mnt/storage/scratch/tmp. Product
tracked files stayed clean; node_modules is the expected untracked symlink.
The external driver creates an isolated Git snapshot for mutations and checks
both the original file hash and empty Git status after every restoration.
Every test start uses busy.py gated with &&, pipefail and tee. busy.py retains
canonical busy detections and excludes only its own process ancestors, which
would otherwise match the driver's command text. No other test process is
whitelisted.
M1-M22 killed. M23 syntax preflight passed; the whole interpreter killer file
reported 3 pass/2 fail. The old driver rejected its runtime JSON SyntaxError as
invalid, so stopped before M24. M23 also made the FIFO child hit its own one
second timeout. No golden package or shell suite started. All 23 restorations
passed hash and clean-status checks. batch.log and mutants/results.json retain
the original classification, not retrospectively edited. Slot handed back in
gander-0031 at 2026-10-21T18:51:50Z. Sage calls this stop 1 (sage-2063).
Next run requires Sage's next clear, after Morgana's row243 singles. M1-M22
stand under sage-2063, pending the separate driver correction. Rerun M23-M29,
then whole bus/WebUI/CLI suites and all ten whole scripts/test-*.sh. A second
stop goes to Sage before any rerun; a third goes to Mos. No acceptance claim.
+23
View File
@@ -0,0 +1,23 @@
M1 KILLED
M2 KILLED
M3 KILLED
M4 KILLED
M5 KILLED
M6 KILLED
M7 KILLED
M8 KILLED
M9 KILLED
M10 KILLED
M11 KILLED
M12 KILLED
M13 KILLED
M14 KILLED
M15 KILLED
M16 KILLED
M17 KILLED
M18 KILLED
M19 KILLED
M20 KILLED
M21 KILLED
M22 KILLED
M23 SURVIVED/INVALID
+15
View File
@@ -0,0 +1,15 @@
import os,subprocess,sys
ancestors=set();pid=os.getpid()
while pid>1 and pid not in ancestors:
ancestors.add(pid)
try:pid=int(open('/proc/%s/stat'%pid).read().rsplit(')',1)[1].split()[1])
except (OSError,ValueError):break
r=subprocess.run(['bash','/mnt/storage/scratch/sage-busy-now.sh'],capture_output=True,text=True)
other=[]
for line in r.stdout.splitlines():
if line.startswith('BUSY '):
try:pid=int(line.split()[1].rstrip(':'))
except ValueError:other.append(line);continue
if pid not in ancestors:other.append(line)
if other:print('\n'.join(other));sys.exit(1)
print('CLEAR (current driver ancestors excluded)')
@@ -0,0 +1,47 @@
diff --git a/agents/gander/work/queue-196/BUILD.md b/agents/gander/work/queue-196/BUILD.md
index 72e359134..1deba067c 100644
--- a/agents/gander/work/queue-196/BUILD.md
+++ b/agents/gander/work/queue-196/BUILD.md
@@ -98,3 +98,5 @@ the fixture verifies no start/stop calls for unsafe mode. M29 removes the check.
Legacy tester-port-invalid wording is restored to avoid an unrelated contract
change. Review covered all changed trust paths and consumers; no further code
changes are planned before the batch. M1-M29 remain prepared and unexecuted.
+
+2026-10-21 sage-2063: driver uses only node --check for syntax validity; runtime test failures count as kills, and the r2 driver records GNU timeout process-group TERM/KILL exits so blocked descendants cannot survive. No rerun before next clear.
diff --git a/agents/gander/work/queue-196/mutants.mjs b/agents/gander/work/queue-196/mutants.mjs
index 95b216d3a..a3342f63d 100644
--- a/agents/gander/work/queue-196/mutants.mjs
+++ b/agents/gander/work/queue-196/mutants.mjs
@@ -56,7 +56,8 @@ try {
const log=(run.stdout??'')+(run.stderr??'');writeFileSync(join(out,id+'.txt'),log);
writeFileSync(at,original);if(hash(readFileSync(at))!==before)throw Error(id+' restore hash failed');
const failed=/[ℹ#]\s*(?:fail|tests failed)\s+[1-9]/.test(log) || /✖/.test(log);
- const killed=run.status!==0&&failed&&!/SyntaxError|ERR_MODULE_NOT_FOUND/.test(log);
+ // Syntax validity is decided exclusively by the node --check preflight above.
+ const killed=run.status!==0&&failed;
const result={id,description,test,args,status:run.status,signal:run.signal,killed,restoredSha256:before};results.push(result);
writeFileSync(join(out,'results.json'),JSON.stringify(results,null,2)+'\n');
console.log(id+' '+(killed?'KILLED':'SURVIVED/INVALID'));
diff --git a/agents/gander/work/queue-196/r2/driver.mjs b/agents/gander/work/queue-196/r2/driver.mjs
index 6f80b6546..508fa4ad3 100644
--- a/agents/gander/work/queue-196/r2/driver.mjs
+++ b/agents/gander/work/queue-196/r2/driver.mjs
@@ -53,12 +53,15 @@ try {
const syntax=spawnSync(file.endsWith('.sh')?'bash':process.execPath,[file.endsWith('.sh')?'-n':'--check',at],{encoding:'utf8'});
if(syntax.status!==0)throw Error(id+' invalid mutation: '+syntax.stderr);
const args=['--test',...(Array.isArray(test)?test:[test])];
- const run=spawnSync('bash',['-c','set -o pipefail; python3 "$G196_BUSY" && "$G196_NODE" "$@" 2>&1 | tee "$G196_LOG"','--',...args],{cwd:scratch,env:{...process.env,G196_NODE:process.execPath,G196_LOG:join(out,id+'.txt')},encoding:'utf8',timeout:60000,maxBuffer:2*1024*1024});
+ const run=spawnSync('timeout',['--signal=TERM','--kill-after=2s','60s','bash','-c','set -o pipefail; python3 "$G196_BUSY" && "$G196_NODE" "$@" 2>&1 | tee "$G196_LOG"','--',...args],{cwd:scratch,env:{...process.env,G196_NODE:process.execPath,G196_LOG:join(out,id+'.txt')},encoding:'utf8',maxBuffer:2*1024*1024});
const log=(run.stdout??'')+(run.stderr??'');writeFileSync(join(out,id+'.txt'),log);
writeFileSync(at,original);const clean=execFileSync('git',['status','--porcelain'],{cwd:scratch,encoding:'utf8'});if(clean.trim())throw Error(id+' dirty restored tree: '+clean);if(hash(readFileSync(at))!==before)throw Error(id+' restore hash failed');
const failed=/[ℹ#]\s*(?:fail|tests failed)\s+[1-9]/.test(log) || /✖/.test(log);
- const killed=run.status!==0&&failed&&!/SyntaxError|ERR_MODULE_NOT_FOUND/.test(log);
- const result={id,description,test,args,status:run.status,signal:run.signal,killed,restoredSha256:before};results.push(result);
+ // Syntax validity is decided exclusively by the node --check preflight above.
+ // GNU timeout owns a process group and terminates its shell, tee and test descendants.
+ const timedOut=run.status===124||run.status===137;
+ const killed=timedOut||(run.status!==0&&failed);
+ const result={id,description,test,args,status:run.status,signal:run.signal,killed,timedOut,timeoutSeconds:60,timeoutKillAfterSeconds:2,processGroupTimeout:true,syntaxStatus:syntax.status,restoredSha256:before};results.push(result);
writeFileSync(join(out,'results.json'),JSON.stringify(results,null,2)+'\n');
console.log(id+' '+(killed?'KILLED':'SURVIVED/INVALID'));
if(!killed) {process.exitCode=1;break;}
@@ -0,0 +1,75 @@
import { guardMutation } from '../mutant-guard.mjs';
// Authorized batch only. Mutate isolated scratch copies, never the candidate or installed tree.
import {mkdtempSync,cpSync,readFileSync,writeFileSync,rmSync,symlinkSync,existsSync,mkdirSync} from 'node:fs';
import {tmpdir} from 'node:os';import {join,resolve} from 'node:path';
import {spawnSync,execFileSync} from 'node:child_process';import {createHash} from 'node:crypto';
import {fileURLToPath} from 'node:url';
const root='/mnt/storage/scratch/tmp/g196-tree-tn651z';
const out='/home/jwoltje/.t3/worktrees/mosaic-stack/gander-row196/agents/gander/work/queue-196/r2/mutants';mkdirSync(out,{recursive:true});
const p='packages/bus/src/principal.mjs',h='packages/bus/src/human.mjs',w='packages/webui/src/tester-socket.mjs',s='packages/bus/src/server.mjs',peer='packages/bus/src/peercred.mjs';
const principal='packages/bus/tests/principal.test.mjs',socket='packages/webui/tests/tester-socket.test.mjs',transport='packages/bus/tests/peer-transport.test.mjs',interpreter='packages/bus/tests/interpreter.test.mjs';
const release=['--test-name-pattern=row196 explicit socket','packages/cli/tests/stack-release.test.mjs'];
const specs=[
['M1', 'packages/webui/src/serve.mjs','tester uses TCP',socket, text=>text.replace('if (tester) return bindTesterSocket(server, testerSocket);',"if (tester) return new Promise(r=>server.listen(0,'127.0.0.1',()=>r(server)));" )],
['M2',w,'socket mode widened',socket,t=>t.replace('chmodSync(temporary, 0o600)','chmodSync(temporary, 0o666)')],
['M3','packages/webui/src/serve.mjs','sign-in accepted on tester',socket,t=>t.replace("if (tester && (Object.hasOwn(req.headers, 'authorization')", "if (false && (Object.hasOwn(req.headers, 'authorization')")],
['M4',p,'copied PID accepted',principal,t=>t.replace('peer.pid !== proof.pid','false')],
['M5',peer,'helper parse error ignored','packages/bus/tests/peercred.test.mjs',t=>t.replace('finish(false, Object.freeze(p));\n } catch { finish(true); }','finish(false, Object.freeze(p));\n } catch { finish(false, {pid:2,uid:0,gid:0}); }')],
['M6',p,'foreign uid accepted',principal,t=>t.replace('peer.uid !== process.getuid()','false')],
['M7',w,'unsafe parent accepted',socket,t=>t.replace('(dir.mode & 0o077)','false')],
['M8',h,'agent environment walk skipped',principal,t=>t.replace('current.env !== null && MARKERS.some','false && MARKERS.some')],
['M9',h,'leaf identity not reread',principal,t=>t.replace('if (again.startTime !== proof.startTime || again.ppid !== p.ppid)','if (false)')],
['M10',p,'Console direct-parent check removed',principal,t=>t.replace("if (parent.uid !== peer.uid || parent.argv[1] !== resolve(paths.console) || parent.argv[2] !== 'serve')",'if (false)')],
['M11',p,'Console basename accepted',principal,t=>t.replace("import { resolve }", "import { resolve, basename }").replace('parent.argv[1] !== resolve(paths.console)','basename(parent.argv[1] ?? "") !== basename(paths.console)')],
['M12',h,'unknown proof identity accepted',principal,t=>t.replace("Object.keys(proof).some((k) => !['pid', 'startTime', 'nonce', 'business'].includes(k))",'false')],
['M13',s,'transient cap leaked',transport,t=>t.replace('broker.disconnect(boundCap)','void(boundCap)')],
['M14',p,'tester walk used for user',principal,t=>t.replace("verifyHuman(checked, { cliPath: paths[kind], launches, readProcess,\n nonceKey: kind === 'user' ? 'MOSAIC_BUS_USER_NONCE' : 'MOSAIC_BUS_CLI_NONCE' })",'verifyTester(checked, {cliPath:paths[kind],launches,readProcess,readNonce:()=>checked.nonce})')],
['M15','scripts/stack-release.sh','TCP retained during conversion',release,t=>t.replace('RUN_CONSOLE=("${without_tester[@]}" --tester-socket)','RUN_CONSOLE=("${BASE_CONSOLE[@]}" --tester-socket)')],
['M16','scripts/stack-release.sh','TCP reopened on downgrade',release,t=>t.replace('RUN_CONSOLE=("${without_tester[@]}") DROP_TESTER="--tester-socket','RUN_CONSOLE=("${without_tester[@]}" --tester-port 7333) DROP_TESTER="--tester-socket')],
['M17','packages/bus/src/broker.mjs','tester switch ignored','packages/bus/tests/tester.test.mjs',t=>t.replace("if (this.#business(record.business).vars?.['bus.tester'] !== true)",'if (false)')],
['M18',p,'user kind becomes owner',principal,t=>t.replace("return { kind: 'user', business, id: proof.user }","return { kind: 'owner', business, id: proof.user }")],
['M19','packages/cli/src/tester-http.mjs','selected instance root ignored',socket,t=>t.replace('socketPath: testerSocketPath(dataRoot)','socketPath: testerSocketPath(dataRoot + "/wrong-instance")')],
['M20',peer,'helper gets another pending socket','packages/bus/tests/peercred.test.mjs',t=>t.replace('export function createPeerReader','function originalPeerReader')+`\nexport function createPeerReader(options){const base=originalPeerReader(options),queue=[];let scheduled=false;return{read(socket){return new Promise((resolve,reject)=>{queue.push({socket,resolve,reject});if(!scheduled){scheduled=true;queueMicrotask(()=>{scheduled=false;const jobs=queue.splice(0);jobs.forEach((job,i)=>base.read(jobs[(i+1)%jobs.length].socket).then(job.resolve,job.reject));});}});},close(){base.close();}};}\n`],
['M21','packages/bus/src/interpreter.mjs','user-owned interpreter accepted',interpreter,t=>t.replace('s.uid !== 0 || (s.mode & 0o022)','false || (s.mode & 0o022)')],
['M22',s,'cap requests invoke helper',transport,t=>t.replace(' if (kind) {',' if (r.cap) await peers.read(socket);\n if (kind) {')],
['M23','packages/bus/src/interpreter.mjs','restart retains stale identity',interpreter,t=>t.replace('export function recordInterpreterStart(directory, record) {','export function recordInterpreterStart(directory, record) {\n try { readFileSync(join(directory,"peercred-starts.jsonl")); return; } catch {}')],
['M24',w,'listener binds final path directly',socket,t=>t.replace("const temporary = join(parent, '.' + randomBytes(4).toString('hex') + '.s');",'const temporary = path;')],
['M25','packages/bus/src/interpreter.mjs','FIFO receipt blocks before type refusal',['--test-name-pattern=non-regular startup receipt',interpreter],t=>t.replace('constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600','constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600')],
['M26',peer,'missing helper accepted at startup',['--test-name-pattern=missing or non-regular peer helper','packages/bus/tests/peercred.test.mjs'],t=>t.replace('if (!s.isFile() || s.isSymbolicLink()) throw Error();','if (false) throw Error();').replace('catch { throw refused(); }','catch {}')],
['M27',peer,'shutdown returns before helpers are reaped',['--test-name-pattern=shutdown waits','packages/bus/tests/peercred.test.mjs'],t=>t.replace(" await Promise.all([...children].map(child => new Promise(resolve => {\n child.once('close', resolve);\n child.kill('SIGKILL');\n })));"," for (const child of children) child.kill('SIGKILL');")],
['M28','scripts/stack-release.sh','proof pair placed after tester socket flag',release,t=>t.replace('case "${without_proof[i]}" in --send | --tester-port | --tester-socket) break','case "${without_proof[i]}" in --send | --tester-port) break')],
['M28b','scripts/stack-release.sh','SSO placed after tester socket flag',['--test-name-pattern=row196 SSO socket','packages/cli/tests/stack-release.test.mjs'],t=>t.replace('case "${kept[i]}" in --send | --tester-port | --tester-socket) break','case "${kept[i]}" in --send | --tester-port) break')],
['M29','scripts/stack-release.sh','unsafe tester path reaches service stop',release,t=>t.replace(t.split('\n').find(line=>line.includes('|| check_refuse tester-socket-path'))+'\n','')],
];
const hash=x=>createHash('sha256').update(x).digest('hex');
const results=[];
for (const [id,file,,,mutate] of specs) {
try { guardMutation(readFileSync(join(root,file),'utf8'),mutate); }
catch(error) { throw Error(id+': '+error.message); }
}
const scratch=mkdtempSync(join(tmpdir(),'g196-mut-'));
try {
for(const name of ['packages','scripts','adapters','roles','contracts','RELEASE','package.json','package-lock.json','.gitignore'])if(existsSync(join(root,name)))cpSync(join(root,name),join(scratch,name),{recursive:true,filter:src=>!src.includes('/node_modules/')&&!src.endsWith('/node_modules')});
symlinkSync(join(root,'node_modules'),join(scratch,'node_modules'));
for(const args of [['init','-q'],['add','.'],['-c','user.name=fixture','-c','user.email=fixture@invalid','-c','commit.gpgsign=false','-c','core.hooksPath=/dev/null','commit','-qm','candidate snapshot']])execFileSync('git',args,{cwd:scratch});
for(const [id,file,description,test,mutate] of specs) {
const at=join(scratch,file),original=readFileSync(at,'utf8'),before=hash(original),changed=guardMutation(original,mutate).text;
if(changed===original)throw Error(id+' did not change bytes');
writeFileSync(at,changed);
const syntax=spawnSync(file.endsWith('.sh')?'bash':process.execPath,[file.endsWith('.sh')?'-n':'--check',at],{encoding:'utf8'});
if(syntax.status!==0)throw Error(id+' invalid mutation: '+syntax.stderr);
const args=['--test',...(Array.isArray(test)?test:[test])];
const run=spawnSync('timeout',['--signal=TERM','--kill-after=2s','60s','bash','-c','set -o pipefail; python3 "$G196_BUSY" && "$G196_NODE" "$@" 2>&1 | tee "$G196_LOG"','--',...args],{cwd:scratch,env:{...process.env,G196_NODE:process.execPath,G196_LOG:join(out,id+'.txt')},encoding:'utf8',maxBuffer:2*1024*1024});
const log=(run.stdout??'')+(run.stderr??'');writeFileSync(join(out,id+'.txt'),log);
writeFileSync(at,original);const clean=execFileSync('git',['status','--porcelain'],{cwd:scratch,encoding:'utf8'});if(clean.trim())throw Error(id+' dirty restored tree: '+clean);if(hash(readFileSync(at))!==before)throw Error(id+' restore hash failed');
const failed=/[ℹ#]\s*(?:fail|tests failed)\s+[1-9]/.test(log) || /✖/.test(log);
// Syntax validity is decided exclusively by the node --check preflight above.
// GNU timeout owns a process group and terminates its shell, tee and test descendants.
const timedOut=run.status===124||run.status===137;
const killed=timedOut||(run.status!==0&&failed);
const result={id,description,test,args,status:run.status,signal:run.signal,killed,timedOut,timeoutSeconds:60,timeoutKillAfterSeconds:2,processGroupTimeout:true,syntaxStatus:syntax.status,restoredSha256:before};results.push(result);
writeFileSync(join(out,'results.json'),JSON.stringify(results,null,2)+'\n');
console.log(id+' '+(killed?'KILLED':'SURVIVED/INVALID'));
if(!killed) {process.exitCode=1;break;}
}
} finally {rmSync(scratch,{recursive:true,force:true});}
@@ -0,0 +1,54 @@
CLEAR (current driver ancestors excluded)
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (49.694568ms)
✖ private tester refuses symlink, unsafe parent and long path without repairs (1.885024ms)
✔ tester cleanup must leave a replacement inode untouched (2.384441ms)
ℹ tests 3
ℹ suites 0
ℹ pass 1
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 121.753018
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:13:1
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (49.694568ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ {
+ address: '127.0.0.1',
+ family: 'IPv4',
+ port: 41395
+ }
- '/mnt/storage/scratch/tmp/gJ3j/g196-w-L7UCok/console/tester.sock'
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:16:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { address: '127.0.0.1', family: 'IPv4', port: 41395 },
expected: '/mnt/storage/scratch/tmp/gJ3j/g196-w-L7UCok/console/tester.sock',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/webui/tests/tester-socket.test.mjs:26:1
✖ private tester refuses symlink, unsafe parent and long path without repairs (1.885024ms)
AssertionError [ERR_ASSERTION]: unsafe tester bind must refuse
at refused (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:11:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:29:2)
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: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,33 @@
CLEAR (current driver ancestors excluded)
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.647432ms)
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.71001ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.285297ms)
✔ human leaf identity is reread after walking the parents (0.167787ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 59.125304
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:22:1
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.71001ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:27:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
CLEAR (current driver ancestors excluded)
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.685528ms)
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.680266ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.286586ms)
✔ human leaf identity is reread after walking the parents (0.150943ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 51.004525
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:22:1
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.680266ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:27:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,31 @@
CLEAR (current driver ancestors excluded)
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.927254ms)
✔ user requires its own nonce and full walk plus immediate same-tree Console parent (0.491561ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.278262ms)
✔ human leaf identity is reread after walking the parents (0.159989ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53.494457
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:15:1
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.927254ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:19:9)
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: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,55 @@
CLEAR (current driver ancestors excluded)
✖ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (9.052406ms)
✔ proof helper error and disconnect during verification never dispatch or bind (1.997863ms)
✔ Client receives explicit proof refusal codes without dispatch, not outcome-unknown (43.098804ms)
✖ asynchronous proof calls release their transient session on success, failure and timeout (2.35303ms)
ℹ tests 4
ℹ suites 0
ℹ pass 2
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 114.087067
✖ failing tests:
test at packages/bus/tests/peer-transport.test.mjs:11:1
✖ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (9.052406ms)
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
assert.ok(disconnected.includes('transient'))
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peer-transport.test.mjs:18:52)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '==',
diff: 'simple'
}
test at packages/bus/tests/peer-transport.test.mjs:46:1
✖ asynchronous proof calls release their transient session on success, failure and timeout (2.35303ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
+ []
- [
- 'success'
- ]
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peer-transport.test.mjs:56:11)
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: [],
expected: [ 'success' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,93 @@
CLEAR (current driver ancestors excluded)
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (2.090667ms)
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.460285ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.249211ms)
✖ human leaf identity is reread after walking the parents (0.26504ms)
ℹ tests 4
ℹ suites 0
ℹ pass 1
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 62.713304
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:15:1
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (2.090667ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /human-required/. Input:
'Error: tester-required'
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:19:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.start (node:internal/test_runner/test:1262:17)
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: tester-required
at refuse (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/src/tester.mjs:13:9)
at verifyTester (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/src/tester.mjs:40:5)
at verifyPrincipal (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/src/principal.mjs:31:24)
at file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:19:20
at getActual (node:assert:580:5)
at strict.throws (node:assert:728:24)
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:19:9)
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),
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
test at packages/bus/tests/principal.test.mjs:22:1
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.460285ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:27:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
test at packages/bus/tests/principal.test.mjs:44:1
✖ human leaf identity is reread after walking the parents (0.26504ms)
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /human-required/. Input:
'Error: tester-required'
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:47:9)
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) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: Error: tester-required
at refuse (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/src/tester.mjs:13:9)
at verifyTester (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/src/tester.mjs:70:5)
at verifyPrincipal (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/src/principal.mjs:31:24)
at file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:47:20
at getActual (node:assert:580:5)
at strict.throws (node:assert:728:24)
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:47:9)
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),
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,40 @@
CLEAR (current driver ancestors excluded)
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (3916.205226ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 4304.679862
✖ failing tests:
test at packages/cli/tests/stack-release.test.mjs:2277:1
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (3916.205226ms)
AssertionError [ERR_ASSERTION]: stack-release: interpreter: {"path":"/usr/bin/python3","sha256":"fixture"}
stack-release: captured the rollback set: /mnt/storage/scratch/tmp/gJ3j/sr-hy3kBG/data/state/stack-rollback/20261021T185103Z-1263a5e3af50
stack-release: drop --tester-port 7333 (replaced by private Unix socket); the target Console does not support it
stack-release: stopped both units
stack-release: stopped store schema: 3g
stack-release: backup: /mnt/storage/scratch/tmp/gJ3j/sr-hy3kBG/data/backups/stack/20261021T185103Z-1263a5e3af50
stack-release: rolling back: install-console
stack-release: stopped both units
stack-release: put back the unit files and the wrapper from /mnt/storage/scratch/tmp/gJ3j/sr-hy3kBG/data/state/stack-rollback/20261021T185103Z-1263a5e3af50
stack-release: gate passed on 901e0b11e9113fbb981db30e6fbe83635540d1f3
stack-release: install-console; rolled back to 901e0b11e9113fbb981db30e6fbe83635540d1f3, which passed the gate
1 !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/cli/tests/stack-release.test.mjs:2290:78)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 1,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,69 @@
CLEAR (current driver ancestors excluded)
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (5111.05339ms)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5664.207303
✖ failing tests:
test at packages/cli/tests/stack-release.test.mjs:2277:1
✖ row196 explicit socket conversion refuses inherited TCP before stop and rollback disables socket (5111.05339ms)
AssertionError [ERR_ASSERTION]: The input was expected to not match the regular expression /--tester-(socket|port)/. Input:
'# Mosaic Console: `node packages/webui/src/cli.mjs serve`, one instance.\n' +
'# Rendered by scripts/webui-service.sh from packages/webui/systemd/; /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd,\n' +
'# /usr/local/bin:/usr/bin:/bin, /usr/bin/node and the serve flags below are filled in at install time.\n' +
'# /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd is the read-only copy `install --copy ID` names, and the flags\n' +
'# pass the canonical checkout as --checkout for the queue read (row 77).\n' +
'# Row 178: optional --proof-header NAME --proof-file PATH checks proxy proof;\n' +
'# the value is read once by serve and never carried in this unit.\n' +
'# Edit the template and reinstall; do not edit the installed unit.\n' +
'#\n' +
'# The Console reads the bus through the human transport, so whoever starts\n' +
"# this unit holds the human's capability. Jason starts and restarts it\n" +
'# (`systemctl --user enable --now mosaic-webui`), and so may Mos under lead\n' +
'# decision 102, which accepts that sends then enter the trail as Jason. Any\n' +
'# other agent starting it works around the human proof (lead decisions 62\n' +
'# and 94). The unit sets no MOSAIC_*, CLAUDE*, CODEX* or PI_* variable.\n' +
'[Unit]\n' +
'Description=Mosaic Console\n' +
'Documentation=file:///mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd/packages/webui/README.md\n' +
'StartLimitIntervalSec=600\n' +
'StartLimitBurst=5\n' +
'\n' +
'[Service]\n' +
'Type=simple\n' +
'WorkingDirectory=/mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd\n' +
'Environment=PATH=/usr/local/bin:/usr/bin:/bin\n' +
'# The server binds 127.0.0.1 only. Usage, config and listen refusals\n' +
'# (a port already in use included) exit 2 and are not retried; fix the\n' +
'# flags or free the port, then start it again.\n' +
'ExecStart=/usr/bin/node /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd/packages/webui/src/cli.mjs serve --checkout /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/repo --port 41915 --business acme --public-host console.example --auth-header x-user --allow-user jason --proof-header x-console-proof --proof-file /mnt/storage/scratch/tmp/gJ3j/sr-K2QFsd/proof.187 --tester-port 7333\n' +
'Restart=on-failure\n' +
'RestartSec=15\n' +
'RestartPreventExitStatus=2\n' +
'# `systemctl --user stop` sends SIGTERM; the server stops accepting and\n' +
'# closes. Open connections that outlast TimeoutStopSec are killed.\n' +
'KillSignal=SIGTERM\n' +
'TimeoutStopSec=30\n' +
'NoNewPrivileges=yes\n' +
'\n' +
'[Install]\n' +
'WantedBy=default.target\n'
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/cli/tests/stack-release.test.mjs:2292:77)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: "# Mosaic Console: `node packages/webui/src/cli.mjs serve`, one instance.\n# Rendered by scripts/webui-service.sh from packages/webui/systemd/; /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd,\n# /usr/local/bin:/usr/bin:/bin, /usr/bin/node and the serve flags below are filled in at install time.\n# /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd is the read-only copy `install --copy ID` names, and the flags\n# pass the canonical checkout as --checkout for the queue read (row 77).\n# Row 178: optional --proof-header NAME --proof-file PATH checks proxy proof;\n# the value is read once by serve and never carried in this unit.\n# Edit the template and reinstall; do not edit the installed unit.\n#\n# The Console reads the bus through the human transport, so whoever starts\n# this unit holds the human's capability. Jason starts and restarts it\n# (`systemctl --user enable --now mosaic-webui`), and so may Mos under lead\n# decision 102, which accepts that sends then enter the trail as Jason. Any\n# other agent starting it works around the human proof (lead decisions 62\n# and 94). The unit sets no MOSAIC_*, CLAUDE*, CODEX* or PI_* variable.\n[Unit]\nDescription=Mosaic Console\nDocumentation=file:///mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd/packages/webui/README.md\nStartLimitIntervalSec=600\nStartLimitBurst=5\n\n[Service]\nType=simple\nWorkingDirectory=/mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd\nEnvironment=PATH=/usr/local/bin:/usr/bin:/bin\n# The server binds 127.0.0.1 only. Usage, config and listen refusals\n# (a port already in use included) exit 2 and are not retried; fix the\n# flags or free the port, then start it again.\nExecStart=/usr/bin/node /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/copies/0d30b8dfbb41b6ca567206d291451b1f5b7d13bd/packages/webui/src/cli.mjs serve --checkout /mnt/storage/scratch/tmp/gJ3j/stack-release-bKqva6/repo --port 41915 --business acme --public-host console.example --auth-header x-user --allow-user jason --proof-header x-console-proof --proof-file /mnt/storage/scratch/tmp/gJ3j/sr-K2QFsd/proof.187 --tester-port 7333\nRestart=on-failure\nRestartSec=15\nRestartPreventExitStatus=2\n# `systemctl --user stop` sends SIGTERM; the server stops accepting and\n# closes. Open connections that outlast TimeoutStopSec are killed.\nKillSignal=SIGTERM\nTimeoutStopSec=30\nNoNewPrivileges=yes\n\n[Install]\nWantedBy=default.target\n",
expected: /--tester-(socket|port)/,
operator: 'doesNotMatch',
diff: 'simple'
}
@@ -0,0 +1,67 @@
CLEAR (current driver ancestors excluded)
✖ the tester binds only when the business sets bus.tester to exactly true (17.53619ms)
✔ a business may not name a role tester, so the tester address is never a role (17.82968ms)
✔ every verb outside the tester list refuses tester-refused and is recorded against the tester (36.71909ms)
✔ a tester message is from tester, needs no prefix, and writes no human.input or authority (14.532304ms)
✔ a tester message to a role no run holds refuses tester-target-not-running and stores nothing (10.145587ms)
✔ a tester message is refused while the run is only launched, and routes once it claims (12.444532ms)
✔ a human message to a role no run holds still waits and tells launch.by (10.928459ms)
✔ a role answers a tester only in reply to a tester message, and the answer never reaches the human (20.898774ms)
✔ the tester reads the human views a reader reads (24.490999ms)
✔ tester proof binds its own CLI, start time and nonce, refuses a managed run and allows an agent (1.344138ms)
✔ neither CLI's nonce proves the other: each proof is bound to its own CLI and nonce variable (0.406093ms)
✔ testerNonce reads only the tester's variable and gives null on any read error (0.198658ms)
✔ an envelope carries exactly one of cap, human and tester; no tester authenticator refuses (118.437146ms)
✖ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (1234.579269ms)
ℹ tests 14
ℹ suites 0
ℹ pass 12
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1628.145277
✖ failing tests:
test at packages/bus/tests/tester.test.mjs:44:1
✖ the tester binds only when the business sets bus.tester to exactly true (17.53619ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: null
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/tester.test.mjs:47:12)
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: 'tester-off' },
operator: 'throws',
diff: 'simple'
}
test at packages/bus/tests/tester.test.mjs:427:1
✖ tester-cli.mjs binds through the runtime when the switch is on, and refuses off or under a run marker (1234.579269ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
+ err: '',
+ out: '[]',
+ status: 0
- err: 'tester-off',
- out: '',
- status: 2
}
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/tester.test.mjs:448: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: { status: 0, out: '[]', err: '' },
expected: { status: 2, out: '', err: 'tester-off' },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,42 @@
CLEAR (current driver ancestors excluded)
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.996624ms)
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (1.266948ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.350185ms)
✔ human leaf identity is reread after walking the parents (0.203698ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 76.258279
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:22:1
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (1.266948ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
{
business: 'demo',
id: 'owner',
+ kind: 'owner'
- kind: 'user'
}
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:24:9)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: { kind: 'owner', business: 'demo', id: 'owner' },
expected: { kind: 'user', business: 'demo', id: 'owner' },
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,24 @@
CLEAR (current driver ancestors excluded)
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (56.676653ms)
✔ private tester refuses symlink, unsafe parent and long path without repairs (1.634686ms)
✔ tester cleanup must leave a replacement inode untouched (1.97195ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 131.19279
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:13:1
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (56.676653ms)
Error: connect ENOENT /mnt/storage/scratch/tmp/gJ3j/g196-w-6wAD6F/wrong-instance/console/tester.sock
at PipeConnectWrap.afterConnect [as oncomplete] (node:net:2021:16) {
errno: -2,
code: 'ENOENT',
syscall: 'connect',
address: '/mnt/storage/scratch/tmp/gJ3j/g196-w-6wAD6F/wrong-instance/console/tester.sock'
}
@@ -0,0 +1,32 @@
CLEAR (current driver ancestors excluded)
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (40.421617ms)
✔ private tester refuses symlink, unsafe parent and long path without repairs (0.920109ms)
✖ tester cleanup must leave a replacement inode untouched (0.939648ms)
ℹ tests 3
ℹ suites 0
ℹ pass 1
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 102.876749
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:13:1
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (40.421617ms)
Error: tester-socket-unsafe
at bindTesterSocket (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/src/tester-socket.mjs:29:102)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:15:12)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
test at packages/webui/tests/tester-socket.test.mjs:36:1
✖ tester cleanup must leave a replacement inode untouched (0.939648ms)
Error: tester-socket-unsafe
at bindTesterSocket (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/src/tester-socket.mjs:29:102)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:42:15)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,53 @@
CLEAR (current driver ancestors excluded)
✖ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (292.257839ms)
✔ helper output and failures fail closed including unsafe gid (2.680068ms)
✔ helper timeout, capacity and shutdown refuse without leaving children (25.751457ms)
✔ missing or non-regular peer helper refuses at construction before opening the bus (0.36369ms)
✖ shutdown waits for killed helper close before returning (0.370388ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 401.085642
✖ failing tests:
test at packages/bus/tests/peercred.test.mjs:15:1
✖ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (292.257839ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
false !== true
at file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peercred.test.mjs:56:12
at Array.forEach (<anonymous>)
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peercred.test.mjs:52:11)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/bus/tests/peercred.test.mjs:98:1
✖ shutdown waits for killed helper close before returning (0.370388ms)
AssertionError [ERR_ASSERTION]: shutdown must await the helper close event
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peercred.test.mjs:106:9)
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: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
CLEAR (current driver ancestors excluded)
✖ system Python is a resolved root-owned ELF, never a shim or home interpreter (2.489468ms)
✔ real system interpreter is available and safely inspected (22.481127ms)
✔ restart receipt appends actual replacement identity and refuses unsafe files (9.851443ms)
✔ activation gate compares fresh startup receipt to the current interpreter identity (1.482676ms)
✔ non-regular startup receipt refuses promptly without waiting on a FIFO peer (33.784543ms)
ℹ tests 5
ℹ suites 0
ℹ pass 4
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 128.30192
✖ failing tests:
test at packages/bus/tests/interpreter.test.mjs:15:1
✖ system Python is a resolved root-owned ELF, never a shim or home interpreter (2.489468ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/interpreter.test.mjs:31:31)
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: /interpreter-unsafe/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
CLEAR (current driver ancestors excluded)
✖ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (7.03352ms)
✔ proof helper error and disconnect during verification never dispatch or bind (1.790604ms)
✔ Client receives explicit proof refusal codes without dispatch, not outcome-unknown (42.381026ms)
✔ asynchronous proof calls release their transient session on success, failure and timeout (28.479209ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 135.242751
✖ failing tests:
test at packages/bus/tests/peer-transport.test.mjs:11:1
✖ proof receives actual peer, cap traffic never invokes helper and transient cap disconnects (7.03352ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
2 !== 1
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peer-transport.test.mjs:20:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,45 @@
CLEAR (current driver ancestors excluded)
✔ system Python is a resolved root-owned ELF, never a shim or home interpreter (2.259714ms)
✔ real system interpreter is available and safely inspected (20.668185ms)
✖ restart receipt appends actual replacement identity and refuses unsafe files (2.992321ms)
✔ activation gate compares fresh startup receipt to the current interpreter identity (1.363356ms)
✖ non-regular startup receipt refuses promptly without waiting on a FIFO peer (1005.398641ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 1086.382437
✖ failing tests:
test at packages/bus/tests/interpreter.test.mjs:38:1
✖ restart receipt appends actual replacement identity and refuses unsafe files (2.992321ms)
SyntaxError: "undefined" is not valid JSON
at JSON.parse (<anonymous>)
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/interpreter.test.mjs:49:48)
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)
test at packages/bus/tests/interpreter.test.mjs:69:1
✖ non-regular startup receipt refuses promptly without waiting on a FIFO peer (1005.398641ms)
AssertionError [ERR_ASSERTION]: unsafe FIFO must refuse without blocking startup
null !== 0
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/interpreter.test.mjs:75:9)
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: null,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
CLEAR (current driver ancestors excluded)
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (59.338551ms)
✔ private tester refuses symlink, unsafe parent and long path without repairs (1.388654ms)
✔ tester cleanup must leave a replacement inode untouched (1.72767ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 128.502051
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:13:1
✖ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (59.338551ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
200 !== 403
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:21:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 200,
expected: 403,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,31 @@
CLEAR (current driver ancestors excluded)
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.691534ms)
✔ user requires its own nonce and full walk plus immediate same-tree Console parent (0.594758ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.274537ms)
✔ human leaf identity is reread after walking the parents (0.160418ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 52.34153
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:15:1
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.691534ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:18:77)
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: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,35 @@
CLEAR (current driver ancestors excluded)
✔ T2 eight proof callers and a racing copier bind the exact accepted socket, which survives helper exit (252.611828ms)
✖ helper output and failures fail closed including unsafe gid (1.307507ms)
✔ helper timeout, capacity and shutdown refuse without leaving children (26.237379ms)
✔ missing or non-regular peer helper refuses at construction before opening the bus (0.252246ms)
✖ shutdown waits for killed helper close before returning (4.713775ms)
ℹ tests 5
ℹ suites 0
ℹ pass 3
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 376.324408
✖ failing tests:
test at packages/bus/tests/peercred.test.mjs:74:1
✖ helper output and failures fail closed including unsafe gid (1.307507ms)
AssertionError [ERR_ASSERTION]: Missing expected rejection.
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/peercred.test.mjs:77:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /peercred-required/,
operator: 'rejects',
diff: 'simple'
}
test at packages/bus/tests/peercred.test.mjs:98:1
✖ shutdown waits for killed helper close before returning (4.713775ms)
[AssertionError [ERR_ASSERTION]: Missing expected rejection.] { generatedMessage: false, code: 'ERR_ASSERTION', actual: undefined, expected: /peercred-required/, operator: 'rejects', diff: 'simple' }
@@ -0,0 +1,31 @@
CLEAR (current driver ancestors excluded)
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.939462ms)
✔ user requires its own nonce and full walk plus immediate same-tree Console parent (0.501573ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.285393ms)
✔ human leaf identity is reread after walking the parents (0.163302ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 56.13375
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:15:1
✖ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.939462ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:18:77)
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: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,30 @@
CLEAR (current driver ancestors excluded)
✔ private tester HTTP serves on 0600 socket in 0700 parent and fixed origin (87.784551ms)
✖ private tester refuses symlink, unsafe parent and long path without repairs (2.258513ms)
✔ tester cleanup must leave a replacement inode untouched (2.916815ms)
ℹ tests 3
ℹ suites 0
ℹ pass 2
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 190.892552
✖ failing tests:
test at packages/webui/tests/tester-socket.test.mjs:26:1
✖ private tester refuses symlink, unsafe parent and long path without repairs (2.258513ms)
AssertionError [ERR_ASSERTION]: unsafe tester bind must refuse
at refused (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:11:9)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/webui/tests/tester-socket.test.mjs:29:2)
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: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,33 @@
CLEAR (current driver ancestors excluded)
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.760997ms)
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.759143ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.301518ms)
✔ human leaf identity is reread after walking the parents (0.176796ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 63.261715
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:22:1
✖ user requires its own nonce and full walk plus immediate same-tree Console parent (0.759143ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:27:10)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,33 @@
CLEAR (current driver ancestors excluded)
✔ principal binds kernel peer PID and uid, maps owner, and never takes identity or role from body (1.588487ms)
✔ user requires its own nonce and full walk plus immediate same-tree Console parent (0.483775ms)
✔ tester deliberately admits agent parents but rejects managed runs and another nonce (0.301471ms)
✖ human leaf identity is reread after walking the parents (0.520477ms)
ℹ tests 4
ℹ suites 0
ℹ pass 3
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 58.566309
✖ failing tests:
test at packages/bus/tests/principal.test.mjs:44:1
✖ human leaf identity is reread after walking the parents (0.520477ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/gJ3j/g196-mut-crpRhv/packages/bus/tests/principal.test.mjs:47:9)
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) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
expected: /human-required/,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,309 @@
[
{
"id": "M1",
"description": "tester uses TCP",
"test": "packages/webui/tests/tester-socket.test.mjs",
"args": [
"--test",
"packages/webui/tests/tester-socket.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "da624f188c69e016ff82e1d1dcae1c579e68a57f0f25d773bb5d8bca56b80ee1"
},
{
"id": "M2",
"description": "socket mode widened",
"test": "packages/webui/tests/tester-socket.test.mjs",
"args": [
"--test",
"packages/webui/tests/tester-socket.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "1c1fe0c795adac97f28502564a75f26d5666d9be5ae51169f7dd5bb7741fad16"
},
{
"id": "M3",
"description": "sign-in accepted on tester",
"test": "packages/webui/tests/tester-socket.test.mjs",
"args": [
"--test",
"packages/webui/tests/tester-socket.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "da624f188c69e016ff82e1d1dcae1c579e68a57f0f25d773bb5d8bca56b80ee1"
},
{
"id": "M4",
"description": "copied PID accepted",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"id": "M5",
"description": "helper parse error ignored",
"test": "packages/bus/tests/peercred.test.mjs",
"args": [
"--test",
"packages/bus/tests/peercred.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "29c88655e8b9aed8db604573c9213fd9f3c40ef35ff04f4eda8fbe8f1b8a32d7"
},
{
"id": "M6",
"description": "foreign uid accepted",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"id": "M7",
"description": "unsafe parent accepted",
"test": "packages/webui/tests/tester-socket.test.mjs",
"args": [
"--test",
"packages/webui/tests/tester-socket.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "1c1fe0c795adac97f28502564a75f26d5666d9be5ae51169f7dd5bb7741fad16"
},
{
"id": "M8",
"description": "agent environment walk skipped",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "0c6d96abe4607cba0b02b0638a2fc217ff8135bac9a47761a0ef015fbe9d3079"
},
{
"id": "M9",
"description": "leaf identity not reread",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "0c6d96abe4607cba0b02b0638a2fc217ff8135bac9a47761a0ef015fbe9d3079"
},
{
"id": "M10",
"description": "Console direct-parent check removed",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"id": "M11",
"description": "Console basename accepted",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"id": "M12",
"description": "unknown proof identity accepted",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "0c6d96abe4607cba0b02b0638a2fc217ff8135bac9a47761a0ef015fbe9d3079"
},
{
"id": "M13",
"description": "transient cap leaked",
"test": "packages/bus/tests/peer-transport.test.mjs",
"args": [
"--test",
"packages/bus/tests/peer-transport.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "830faa9a76150b59189cde395ec8deeb768e4b0d45ed262a57bb6fc6e459abb4"
},
{
"id": "M14",
"description": "tester walk used for user",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"id": "M15",
"description": "TCP retained during conversion",
"test": [
"--test-name-pattern=row196 explicit socket",
"packages/cli/tests/stack-release.test.mjs"
],
"args": [
"--test",
"--test-name-pattern=row196 explicit socket",
"packages/cli/tests/stack-release.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "b916a2793311d21e3ce7117e9b5e0768e6e33c841fa3aa39fb7ffc9c5974acd2"
},
{
"id": "M16",
"description": "TCP reopened on downgrade",
"test": [
"--test-name-pattern=row196 explicit socket",
"packages/cli/tests/stack-release.test.mjs"
],
"args": [
"--test",
"--test-name-pattern=row196 explicit socket",
"packages/cli/tests/stack-release.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "b916a2793311d21e3ce7117e9b5e0768e6e33c841fa3aa39fb7ffc9c5974acd2"
},
{
"id": "M17",
"description": "tester switch ignored",
"test": "packages/bus/tests/tester.test.mjs",
"args": [
"--test",
"packages/bus/tests/tester.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "e6f33bac95a40c9d52d67f297eec361cd0f30da5d3c28057468ee5d56c3ae7fc"
},
{
"id": "M18",
"description": "user kind becomes owner",
"test": "packages/bus/tests/principal.test.mjs",
"args": [
"--test",
"packages/bus/tests/principal.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "32ef39d80b523bdd586c29620d7a752c309c8e1aa70befc1414125468f3411dd"
},
{
"id": "M19",
"description": "selected instance root ignored",
"test": "packages/webui/tests/tester-socket.test.mjs",
"args": [
"--test",
"packages/webui/tests/tester-socket.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "4bd7bdf0898844684ebf9444b110d2c5b6fa874b2681183e54f1f62f97823192"
},
{
"id": "M20",
"description": "helper gets another pending socket",
"test": "packages/bus/tests/peercred.test.mjs",
"args": [
"--test",
"packages/bus/tests/peercred.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "29c88655e8b9aed8db604573c9213fd9f3c40ef35ff04f4eda8fbe8f1b8a32d7"
},
{
"id": "M21",
"description": "user-owned interpreter accepted",
"test": "packages/bus/tests/interpreter.test.mjs",
"args": [
"--test",
"packages/bus/tests/interpreter.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "d5d98a9133a1b449acc48143fd559d890a470c6ea210b30b9b70e491e6cd855a"
},
{
"id": "M22",
"description": "cap requests invoke helper",
"test": "packages/bus/tests/peer-transport.test.mjs",
"args": [
"--test",
"packages/bus/tests/peer-transport.test.mjs"
],
"status": 1,
"signal": null,
"killed": true,
"restoredSha256": "830faa9a76150b59189cde395ec8deeb768e4b0d45ed262a57bb6fc6e459abb4"
},
{
"id": "M23",
"description": "restart retains stale identity",
"test": "packages/bus/tests/interpreter.test.mjs",
"args": [
"--test",
"packages/bus/tests/interpreter.test.mjs"
],
"status": 1,
"signal": null,
"killed": false,
"restoredSha256": "d5d98a9133a1b449acc48143fd559d890a470c6ea210b30b9b70e491e6cd855a"
}
]
@@ -0,0 +1 @@
fb96c71db3715d7ea766b278377c4f215ba1537b 0000000000000000000000000000000000000000
@@ -0,0 +1 @@
294d45c6c8494e83e4276a8c554fcbe85674f3d0 0000000000000000000000000000000000000000
@@ -0,0 +1,2 @@
/mnt/storage/scratch/tmp/g196-tree-tn651z
/mnt/storage/scratch/tmp/gJ3j

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