docs(review): row 47 round 1 review packet, approve (darkwing)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-09 23:06:17 -05:00
co-authored by Claude Opus 5.5
parent 46b87c5df2
commit 3b5024216f
41 changed files with 2796 additions and 0 deletions
@@ -0,0 +1,6 @@
dfd9be08c4a31ee536bbcd9bfd8e73cd8baa4114086b55845627ea0c97023e2a agents/dewey/work/queue-47/evidence.md
061ca09644d03690c92b2d09f3b13d407f194fe0b590125692c359d10bace395 packages/conversation/tests/claim.test.mjs
9bb32c730e6fd1d6648ff623cd6ded71cda300ede2d618e28ee6b682626d4d01 packages/conversation/tests/cohort.test.mjs
896f09586916ffdb399b2b5bd04770473a6dd62938d1e9cc0a7e0239f32a36ef packages/conversation/tests/flows.test.mjs
ca003075749dec9368c91f282c8218d602eeb8c6290282a884876be5af2d87e6 packages/conversation/tests/harness.mjs
2ae5d7b8119f29654e1672d1948992be79e973049a50da99250de7ade26b2071 packages/conversation/tests/races.test.mjs
@@ -0,0 +1,6 @@
agents/dewey/work/queue-47/evidence.md
packages/conversation/tests/claim.test.mjs
packages/conversation/tests/cohort.test.mjs
packages/conversation/tests/flows.test.mjs
packages/conversation/tests/harness.mjs
packages/conversation/tests/races.test.mjs
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
export TMPDIR=~/darkwing-scratch/r47a/tmp DOCKER_HOST=unix:///nonexistent.sock
cd ~/darkwing-scratch/r47a/wt
O=~/darkwing-scratch/r47a/out
: > $O/summary.txt
shims() { ps -eo pid,args | grep -F "$TMPDIR" | grep -F shim.mjs | grep -v grep | wc -l; }
for p in conversation webui; do
node --test "packages/$p/tests/*.test.mjs" > $O/node-$p.txt 2>&1; e=$?
echo "node-$p exit=$e $(grep -E '^ℹ (pass|fail)' $O/node-$p.txt | tr '\n' ' ') shims-left=$(shims)" >> $O/summary.txt
done
for f in scripts/test-*.sh; do
s=$(basename $f .sh)
$f > $O/$s.txt 2>&1; e=$?
echo "$s exit=$e $(grep -E 'passed, [0-9]+ failed' $O/$s.txt | tail -1)" >> $O/summary.txt
done
echo "shims-left-end=$(shims)" >> $O/summary.txt
echo DONE >> $O/summary.txt
@@ -0,0 +1,196 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (230.761469ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (35.283345ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.246256ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (666.451415ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (419.502539ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (259.427764ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (243.237404ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (216.032408ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.493472ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (12101.571707ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (35852.52336ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (364.603499ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (157.89886ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (696.888506ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (353.745861ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (432.046961ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (176.123441ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (237.115041ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (443.951742ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1335.729283ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (399.160544ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (128.725923ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (69.244187ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.523388ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.102658ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.859532ms)
✔ no shim from this file's tests is left running for the after hook (25.021175ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2907.900269ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (662.084573ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2648.481561ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4971.590724ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2671.091036ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2543.899927ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2497.760482ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5061.043083ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (944.475903ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (579.802852ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (510.816848ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (853.259964ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (570.67324ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (748.384126ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3374.04811ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (27.290911ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (633.85929ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (650.888402ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (103.278418ms)
✔ no shim from this file's tests is left running for the after hook (26.447839ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (219.066131ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (486.569612ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (237.738849ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (344.619515ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (230.932458ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.452415ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (242.54138ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (326.810099ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (250.642243ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (222.955355ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (302.713109ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (343.538094ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.0646ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (302.801926ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (249.456734ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (315.374632ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (225.411308ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.373986ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.257853ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.32094ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.481082ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.859499ms)
✖ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (1.169348ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (6.289639ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.414834ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.419486ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.101127ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (224.439992ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (265.687996ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (602.750029ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (980.633425ms)
✔ H4: self-takeover is refused (211.637746ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (506.871423ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1488.148474ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (257.919317ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (807.188663ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (345.183361ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (230.52151ms)
✔ H13: a retry with the same request ID and different text is refused (187.355213ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (695.840348ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (392.932221ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (219.444268ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (986.019029ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (368.285501ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (809.335038ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (217.250935ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (337.785107ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.629978ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (681.233021ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (588.806469ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (712.524709ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2943.496164ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (678.292339ms)
✔ no shim from this file's tests is left running for the after hook (26.16412ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (8.500467ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.946212ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (3.038266ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.460723ms)
✔ F1: an unreadable fork is never merged into another branch's history (2.377455ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (3.037382ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.323094ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.118799ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.420763ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (6.4095ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.711012ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (6.202576ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (8.351461ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.80748ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.879545ms)
✔ F9: registrations never add or redirect a root (1.819782ms)
✔ F10: a header cwd naming another project is refused (4.267714ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.734978ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.160641ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.942174ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.400315ms)
✔ F13: compaction is a marker in place, then the retained content (0.72091ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (541.577382ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.290547ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.426317ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.189166ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.018126ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.785298ms)
✔ every page and cursor is a valid CHAT-01 record (734.907993ms)
✔ the engine pin holds for the installed package (3.14919ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (289.737014ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (607.426468ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (240.104396ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (819.982387ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (520.700411ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (292.365153ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.764736ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (410.320626ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (234.058416ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (327.369494ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (752.757501ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1969.460632ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (228.669663ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (312.846973ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (221.554358ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (269.807001ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (554.763895ms)
✔ N10: fake conformance (32.742057ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (465.53822ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (311.07635ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (366.274019ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (379.816865ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (381.8195ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (380.794236ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (215.48326ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (368.478508ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (764.711894ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (601.979351ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (519.115829ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (210.54534ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (191.338939ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (189.870649ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (237.835355ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (157.620797ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 58028.081921
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:714:1
✖ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (1.169348ms)
AssertionError [ERR_ASSERTION]: ["\u0014","\u000f","hi\r"]
+ actual - expected
+ []
- [
- 'hi'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/flows.test.mjs:726:12)
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: [],
expected: [ 'hi' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1 @@
@@ -0,0 +1,228 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (253.899662ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.284113ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.860532ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (734.363814ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (524.38725ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (252.799759ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (222.741713ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (234.15361ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.493404ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (12258.436675ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (32349.669618ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (272.642236ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (147.477768ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (480.56355ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (338.33334ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (399.586115ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (149.751919ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (236.949804ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (387.390616ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1038.478309ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (237.992787ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (119.660597ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (80.974265ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.444071ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.109812ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.86441ms)
✖ no shim from this file's tests is left running for the after hook (5035.26209ms)
✖ /home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs (5043.339958ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3302.420094ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (472.970914ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2796.728057ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5105.353597ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2543.819351ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2510.616024ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2475.701534ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5063.223619ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (771.359208ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (645.101545ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (506.62451ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (829.124385ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (420.127538ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (685.425895ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3650.969116ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (50.323904ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (469.843386ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (401.628845ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (83.176682ms)
✔ no shim from this file's tests is left running for the after hook (28.57982ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (249.951436ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (230.734553ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (529.84075ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (468.686749ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (237.030606ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.410276ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (217.020169ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (315.308644ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (240.266481ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (216.341803ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (277.942621ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (211.881183ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.956364ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (209.424433ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (233.942839ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (240.21204ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (202.309814ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.473741ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.344905ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.345713ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.509785ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.230161ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.535526ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.767751ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.427819ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.374154ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.124458ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (253.515467ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (294.243499ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (643.849856ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (1035.077009ms)
✔ H4: self-takeover is refused (220.514251ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (513.050569ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1044.31887ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (200.880629ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (646.165518ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (299.259321ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (204.891687ms)
✔ H13: a retry with the same request ID and different text is refused (210.61934ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (608.700028ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (293.875373ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (207.822911ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (907.20909ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (383.22996ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (780.261155ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (314.578713ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (492.689541ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.638149ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (745.520193ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (614.542673ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (775.55878ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2983.804441ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (663.639159ms)
✔ no shim from this file's tests is left running for the after hook (29.149662ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.901379ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.757561ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (3.006657ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (4.500776ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.961886ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.558189ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.380572ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.109752ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.787822ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.931861ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.60544ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.016363ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.376863ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.418495ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.608935ms)
✔ F9: registrations never add or redirect a root (2.387521ms)
✔ F10: a header cwd naming another project is refused (4.782722ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.704275ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.158477ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.217859ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.398999ms)
✔ F13: compaction is a marker in place, then the retained content (0.772587ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (562.681896ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.139319ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.62126ms)
✔ unknown conversations, empty files and non-Pi files refuse (3.23556ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.209457ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.226314ms)
✔ every page and cursor is a valid CHAT-01 record (787.451304ms)
✔ the engine pin holds for the installed package (2.048735ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (285.366263ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (579.211254ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (272.40564ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (577.591802ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (946.761723ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (282.830757ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.001149ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (399.29855ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (230.660155ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (301.688325ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (545.551867ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1879.973217ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (206.93602ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (301.335987ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (216.121148ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (246.935251ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (338.923698ms)
✔ N10: fake conformance (32.84403ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (463.157846ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (304.192068ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (300.327975ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (383.033758ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (376.977901ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (372.577696ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (279.493416ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (661.735582ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (826.188509ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (576.149478ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (487.44522ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (249.137762ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (216.081275ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (213.406251ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (229.431708ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (235.869084ms)
ℹ tests 166
ℹ suites 0
ℹ pass 164
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 60982.361112
✖ failing tests:
test at packages/conversation/tests/claim.test.mjs:714:1
✖ no shim from this file's tests is left running for the after hook (5035.26209ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3459119,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock',
+ unit: 'mosaic-chat-cede1ee324e5b263ef2671152'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs:715:10)
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: [ { pid: 3459119, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock', unit: 'mosaic-chat-cede1ee324e5b263ef2671152' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/conversation/tests/claim.test.mjs:28:1
✖ /home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs (5043.339958ms)
AssertionError [ERR_ASSERTION]: no shim outlives this file (#1533)
+ actual - expected
+ [
+ {
+ pid: 3459119,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock',
+ unit: 'mosaic-chat-cede1ee324e5b263ef2671152'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs:32:10)
at async TestHook.run (node:internal/test_runner/test:1409:7)
at async Test.runHook (node:internal/test_runner/test:1289:9)
at async after (node:internal/test_runner/test:1342:9)
at async Test.run (node:internal/test_runner/test:1428:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: [ { pid: 3459119, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock', unit: 'mosaic-chat-cede1ee324e5b263ef2671152' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1 @@
3459119 /usr/bin/node /home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/src/shim.mjs --socket /home/jwoltje/darkwing-scratch/r47a/tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock -- /usr/bin/node /home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/fake-pi.mjs --mode rpc --no-extensions --no-prompt-templates --no-themes --no-approve --session /home/jwoltje/darkwing-scratch/r47a/tmp/mut-afterleak/chat03-lQNQDH/f55/proj/.pi/state/fixture-seat/sessions/s1.jsonl
@@ -0,0 +1,205 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (197.049296ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (30.2567ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.869101ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (345.31181ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (422.999536ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (331.462139ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (269.896862ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (226.565456ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.459269ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11410.993529ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (33468.852499ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (278.035112ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (156.636284ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (648.670807ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (282.454964ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (359.105989ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (98.174892ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (200.317068ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (257.96069ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (719.726591ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (173.907622ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (88.540111ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (69.986196ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.40838ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.037676ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.732583ms)
✔ no shim from this file's tests is left running for the after hook (25.039017ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2950.569631ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (451.497915ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2781.966793ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4839.738336ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2514.094444ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2517.229628ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2437.649908ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5021.280751ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (959.412745ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (554.748075ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (598.302911ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (646.325558ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (328.297709ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (524.693662ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3271.735696ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (33.382515ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (360.808345ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (361.996968ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.528117ms)
✔ no shim from this file's tests is left running for the after hook (28.074449ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (194.578162ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (188.929563ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (186.630794ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (310.239901ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (250.607896ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.453839ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (325.324105ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (366.090081ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (225.087215ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (192.31515ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (209.298056ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (218.342388ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.97722ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (207.199358ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (226.432261ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (251.463208ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (201.15355ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.426921ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.272057ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.326321ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.493318ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.929698ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.489135ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.824779ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.428027ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.351569ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.110321ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (237.286641ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (230.878494ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (277.834974ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (1060.632333ms)
✔ H4: self-takeover is refused (224.791538ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (446.212191ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1052.06213ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (231.205097ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (660.604274ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (278.293002ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (207.032141ms)
✔ H13: a retry with the same request ID and different text is refused (196.251993ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (727.62471ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (370.396901ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (192.209941ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (876.947035ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (321.051889ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (758.666111ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (360.368072ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (335.060909ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.612822ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (661.027296ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (566.594343ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (708.560077ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2938.033537ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (655.993875ms)
✖ no shim from this file's tests is left running for the after hook (5056.941433ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (9.45792ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.653489ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.771711ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.721783ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.744085ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.950975ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.033733ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.299469ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.631965ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.083972ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.777326ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.450746ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.504334ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (11.504271ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.590604ms)
✔ F9: registrations never add or redirect a root (2.60161ms)
✔ F10: a header cwd naming another project is refused (5.312424ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.833801ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.576443ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.031185ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.540383ms)
✔ F13: compaction is a marker in place, then the retained content (1.055277ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (495.761506ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.530977ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.508889ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.332009ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.215739ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.130551ms)
✔ every page and cursor is a valid CHAT-01 record (791.701227ms)
✔ the engine pin holds for the installed package (2.128908ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (263.839834ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (503.901361ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (264.144541ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (460.491405ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (459.366987ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (331.634788ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.888495ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (490.102814ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (213.93758ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (293.015714ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (477.191806ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1888.727614ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (203.998308ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (316.971117ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (282.097455ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (256.140276ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (477.083588ms)
✔ N10: fake conformance (32.934542ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (450.496013ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (281.090048ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (298.332038ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (355.356665ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (358.037348ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (524.641792ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (195.587322ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (285.42895ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (632.572932ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (497.638133ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (464.937522ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (222.460525ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (153.565035ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (166.396372ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (224.677414ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (248.72541ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53159.579855
✖ failing tests:
test at packages/conversation/tests/races.test.mjs:902:1
✖ no shim from this file's tests is left running for the after hook (5056.941433ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3568275,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-h22noreap/chat03-9zq6wq/f33/sock/shim-cfdfa3433345.sock',
+ unit: 'mosaic-chat-cfdfa3433345b9db00c5d3051'
+ },
+ {
+ pid: 3568508,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-h22noreap/chat03-9zq6wq/f33/sock/shim-cd552eea1f4f.sock',
+ unit: 'mosaic-chat-cd552eea1f4f3842f83cdde65'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/races.test.mjs:903:10)
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: [ { pid: 3568275, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-h22noreap/chat03-9zq6wq/f33/sock/shim-cfdfa3433345.sock', unit: 'mosaic-chat-cfdfa3433345b9db00c5d3051' }, { pid: 3568508, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-h22noreap/chat03-9zq6wq/f33/sock/shim-cd552eea1f4f.sock', unit: 'mosaic-chat-cd552eea1f4f3842f83cdde65' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,173 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (210.899084ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (29.31549ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.448014ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (343.333366ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (402.030356ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (229.874766ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (225.367275ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (235.538338ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.510001ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11107.098528ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (34234.25014ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (257.480277ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (143.029294ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (640.332526ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (280.540927ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (344.303521ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (100.06914ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (193.668152ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (260.464092ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (719.818824ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (176.592675ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (84.681532ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (68.297527ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.391231ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.076531ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.691738ms)
✔ no shim from this file's tests is left running for the after hook (24.993244ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2926.997693ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (457.071569ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2764.632052ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4847.049015ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2624.498105ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2504.544818ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2404.966985ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5364.683799ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (760.038648ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (786.324974ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (488.827654ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (648.931098ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (535.972437ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (721.376017ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3325.570656ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (21.89574ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (369.227467ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (361.962609ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (85.614309ms)
✔ no shim from this file's tests is left running for the after hook (26.492113ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (200.045294ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (185.609228ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (183.997293ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (339.6938ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (186.03016ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.412374ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (172.208693ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (328.732036ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (228.009808ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (195.782075ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (215.719368ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (212.909605ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.147188ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (201.279039ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (220.788048ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (250.112806ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (194.326465ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.405847ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.253462ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.309444ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.464404ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.10644ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.456697ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.716844ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.439276ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.339054ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.102817ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (219.772943ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (247.163097ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (270.012137ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (771.823938ms)
✔ H4: self-takeover is refused (243.410382ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (456.213319ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1034.647794ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (233.37577ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (707.537006ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (265.35379ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (194.180998ms)
✔ H13: a retry with the same request ID and different text is refused (182.89918ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (540.720156ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (289.805567ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (188.196907ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (876.45361ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (364.162773ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (740.85462ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (196.962746ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (339.055104ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.612248ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (682.735127ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (554.789216ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (707.976793ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2900.763338ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (558.159465ms)
✔ no shim from this file's tests is left running for the after hook (26.551198ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.823937ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.618154ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.043225ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.654588ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.853218ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.892136ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.156663ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.916826ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.844588ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (6.50136ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.149952ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.701323ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.772768ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.21024ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.860667ms)
✔ F9: registrations never add or redirect a root (1.777036ms)
✔ F10: a header cwd naming another project is refused (7.824365ms)
✔ F11: parentSession renders with a marker and the parent is never opened (1.222698ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (6.650439ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.648522ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.562117ms)
✔ F13: compaction is a marker in place, then the retained content (0.796403ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (535.261071ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.446942ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.359388ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.192692ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.017901ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.868379ms)
✔ every page and cursor is a valid CHAT-01 record (732.019885ms)
✔ the engine pin holds for the installed package (2.291951ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (273.447098ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (580.641872ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (246.192927ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (450.247343ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (488.211935ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (216.62409ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (19.979108ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (376.258015ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (224.504275ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (299.424625ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (480.041118ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1885.455756ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (186.703798ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (270.4736ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (187.391958ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (156.128647ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (401.559088ms)
✔ N10: fake conformance (32.345984ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (444.806874ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (270.811479ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (301.230822ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (369.074885ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (346.504687ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (343.343183ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (199.261583ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (316.774796ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (626.019436ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (483.717534ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (433.204563ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (199.668666ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (166.130174ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (200.872925ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (212.757521ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (199.990162ms)
ℹ tests 165
ℹ suites 0
ℹ pass 165
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53439.906087
@@ -0,0 +1,200 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (202.054486ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (29.094903ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (20.521371ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (380.265738ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (383.77194ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (235.297995ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (178.708428ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (191.616901ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.488977ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11459.462545ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (32312.775307ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (262.995988ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (145.295044ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (620.604825ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (277.742463ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (357.582737ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (135.674806ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (194.274656ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (300.792706ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1028.727876ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (213.849293ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (92.99746ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (67.615031ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.586644ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.081281ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.22829ms)
✔ no shim from this file's tests is left running for the after hook (24.991113ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2945.629077ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (455.109288ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2781.203996ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4882.741961ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2545.016691ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2654.869004ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2370.187002ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5552.934552ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (788.269939ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (712.605732ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (806.214727ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (692.985289ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (332.069099ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (463.128132ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3254.545765ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (21.722265ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (278.326856ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (292.170135ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (69.124067ms)
✖ no shim from this file's tests is left running for the after hook (5042.966648ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (194.790194ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (190.066625ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (182.2687ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (315.705683ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (182.793024ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.416493ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (186.923301ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (282.324841ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (210.171112ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (186.893319ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (224.590288ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (220.473361ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.02041ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (204.112998ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (214.878204ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (252.623854ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (199.609442ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.42963ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.296061ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.320954ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.481725ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.849683ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.444594ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.71464ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.328147ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.329988ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.104019ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (224.528016ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (243.085787ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (269.197312ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (816.20328ms)
✔ H4: self-takeover is refused (178.587331ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (438.883667ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1060.31653ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (223.711706ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (662.952093ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (293.495194ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (193.47591ms)
✔ H13: a retry with the same request ID and different text is refused (197.665643ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (592.405485ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (295.824203ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (250.953389ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (978.022423ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (370.058061ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (768.589881ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (202.773991ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (346.678335ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.658992ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (671.389147ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (587.714656ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (707.767483ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2964.584779ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (590.347364ms)
✔ no shim from this file's tests is left running for the after hook (25.878234ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.168172ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.700461ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.581868ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.428857ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.316679ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.848228ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.202433ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.615224ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.181542ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.451603ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.915774ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.416033ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.368711ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.044047ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.730808ms)
✔ F9: registrations never add or redirect a root (1.692061ms)
✔ F10: a header cwd naming another project is refused (4.311864ms)
✔ F11: parentSession renders with a marker and the parent is never opened (1.06521ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.958754ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.190111ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.705023ms)
✔ F13: compaction is a marker in place, then the retained content (0.921494ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (503.65243ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.346234ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.423213ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.18304ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.023734ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.835462ms)
✔ every page and cursor is a valid CHAT-01 record (744.075418ms)
✔ the engine pin holds for the installed package (1.714045ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (266.329572ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (514.394265ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (240.931026ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (452.163235ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (457.981922ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (220.293101ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.779992ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (324.014318ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (193.956062ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (293.795022ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (512.045177ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1880.726547ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (195.495573ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (276.81879ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (227.736397ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (235.102568ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (436.369737ms)
✔ N10: fake conformance (32.547618ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (478.496775ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (300.483864ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (342.840723ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (364.51853ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (375.392696ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (354.980863ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (202.914908ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (290.893238ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (727.756146ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (508.176217ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (487.479705ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (192.908547ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (172.475922ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (181.295263ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (214.730578ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (212.086389ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 52233.099536
✖ failing tests:
test at packages/conversation/tests/cohort.test.mjs:748:1
✖ no shim from this file's tests is left running for the after hook (5042.966648ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3538170,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-k19leak/chat03-GfMOSY/f24/sock/k19.sock',
+ unit: 'mosaic-chat-ccf3b9f715753becf8b551870'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/cohort.test.mjs:749:10)
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: [ { pid: 3538170, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-k19leak/chat03-GfMOSY/f24/sock/k19.sock', unit: 'mosaic-chat-ccf3b9f715753becf8b551870' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,6 @@
agents/dewey/work/queue-47/evidence.md: OK
packages/conversation/tests/claim.test.mjs: OK
packages/conversation/tests/cohort.test.mjs: OK
packages/conversation/tests/flows.test.mjs: OK
packages/conversation/tests/harness.mjs: OK
packages/conversation/tests/races.test.mjs: OK
@@ -0,0 +1,33 @@
# mutate.py <id>: apply one named mutant (each edit exact text, must match once). Run from the tree root.
import sys
C = "packages/conversation/"
NOPROBE = (C + "tests/claim.test.mjs", " // The force stop leaves the probe's shim running: nothing sends `release`.\n reap(probe);\n", "")
M = {
"Mr": [(C + "src/terminal.mjs", " holds = this.held !== null;\n", " holds = false;\n")],
"noprobe": [NOPROBE],
"now13": [(C + "tests/claim.test.mjs", " // The force stop leaves the shim running: nothing sends `release`.\n reap(fx);\n", "")],
"afterleak": [NOPROBE, (C + "tests/claim.test.mjs", " for (const fx of reaped) reap(fx);\n killShims();\n const left", " const left")],
# The sweep alone, with no records: noprobe, and after doesn't reap the fixtures.
"sweeponly": [NOPROBE, (C + "tests/claim.test.mjs", " for (const fx of reaped) reap(fx);\n killShims();\n const left", " killShims();\n const left")],
# The sweep kills by PID only, no scope kill: noprobe and after without reap.
"sweeppid": [NOPROBE, (C + "tests/claim.test.mjs", " for (const fx of reaped) reap(fx);\n killShims();\n const left", " killShims();\n const left"),
(C + "tests/harness.mjs", " if (s.unit) spawnSync(\"systemctl\", [\"--user\", \"kill\", \"--signal=SIGKILL\", `${s.unit}.scope`], { stdio: \"ignore\", timeout: 5000 });\n", "")],
# K19 leaves its scope: no kill, no release.
"k19leak": [(C + "tests/cohort.test.mjs",
" const killed = await shimRequest(socketPath, \"kill\", { timeoutMs: 5000 }, 8000);\n const released = killed.ok ? await shimRequest(socketPath, \"release\") : killed;\n if (!released.ok) spawnSync(\"systemctl\", [\"--user\", \"kill\", \"--signal=SIGKILL\", `${unitName}.scope`], { stdio: \"ignore\", timeout: 5000 });\n await proc.exited;\n assert.equal(killed.ok, true, JSON.stringify(killed));\n assert.equal(released.ok, true, JSON.stringify(released));\n",
" void proc;\n")],
# H23 doesn't reap its fixture.
"h23noreap": [(C + "tests/races.test.mjs", " await b.exited;\n reap(fx);\n});\n\n// Last in the file.", " await b.exited;\n});\n\n// Last in the file.")],
# H22 (a scope, force-stopped, then relaunched) doesn't reap its fixture.
"h22noreap": [(C + "tests/races.test.mjs", " await b.exited;\n reap(fx);\n});\n\ntest(\"H23", " await b.exited;\n});\n\ntest(\"H23")],
# reap() without the sweep: the backstop alone.
"reapnosweep": [(C + "tests/harness.mjs", " // calls above didn't reach: neither result is checked.\n killShims(fx.base);\n", " // calls above didn't reach: neither result is checked.\n")],
}
if "--files" in sys.argv:
print(" ".join(sorted({f for f, _, _ in M[sys.argv[1]]}))); sys.exit(0)
for f, old, new in M[sys.argv[1]]:
s = open(f).read()
n = s.count(old)
if n != 1: sys.exit(f"{sys.argv[1]}: {n} matches in {f}")
open(f, "w").write(s.replace(old, new))
print(f)
@@ -0,0 +1,200 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (282.937597ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (50.093746ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.74016ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (520.780811ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (443.539556ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (249.091677ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (215.670313ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (234.14316ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.617411ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (13219.243338ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (35043.20244ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (373.142977ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (475.241878ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (622.145753ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (445.37713ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (519.884557ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (125.014355ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (212.933884ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (326.08032ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (906.246954ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (213.52989ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (119.253093ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (78.067386ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.440182ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.111808ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.13747ms)
✖ no shim from this file's tests is left running for the after hook (5046.252437ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3104.602351ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (697.532535ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (3047.740898ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5198.390202ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2519.122514ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2573.295712ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2493.738315ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5195.842609ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (859.9822ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (742.744435ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (561.834231ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (901.421249ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (463.846568ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (715.309839ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3391.404111ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (26.042965ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (523.024463ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (330.55586ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.540287ms)
✔ no shim from this file's tests is left running for the after hook (26.566716ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (286.168512ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (377.747436ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (215.200155ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (326.602654ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (230.455005ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.440313ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (215.740333ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (330.41535ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (245.354601ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (225.812654ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (246.631562ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (252.64731ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.067682ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (451.425124ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (316.454708ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (311.885054ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (292.804535ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.420948ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.255838ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.327912ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.481876ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.289223ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.486287ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.783371ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.39157ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.34537ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.0984ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (272.955772ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (340.814472ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (451.028642ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (908.055698ms)
✔ H4: self-takeover is refused (227.773618ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (506.331615ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1553.972775ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (374.170056ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (1281.70912ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (388.891311ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (215.368506ms)
✔ H13: a retry with the same request ID and different text is refused (236.172724ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (690.882483ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (337.15436ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (229.432724ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1197.075582ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (505.809962ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (1003.411377ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (186.620806ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (304.074651ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.660476ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (689.760131ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (579.201376ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (711.583219ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2954.117441ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (676.012514ms)
✔ no shim from this file's tests is left running for the after hook (25.886638ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.588771ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.703565ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.787985ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.64103ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.443367ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.991457ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.794714ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.278564ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.372104ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.185505ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.862704ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.223447ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.438179ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.015533ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.968255ms)
✔ F9: registrations never add or redirect a root (2.093746ms)
✔ F10: a header cwd naming another project is refused (4.510677ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.728622ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.254575ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.091962ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.374193ms)
✔ F13: compaction is a marker in place, then the retained content (0.933001ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (509.469711ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.303588ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (4.26981ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.105312ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.036966ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.788252ms)
✔ every page and cursor is a valid CHAT-01 record (811.987754ms)
✔ the engine pin holds for the installed package (1.836325ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (267.238939ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (598.801528ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (276.924431ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (749.402112ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (498.688556ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (266.07953ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.947376ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (394.118829ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (232.943051ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (324.480381ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (655.833704ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (2170.238581ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (305.14733ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (342.485459ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (180.111325ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (290.142357ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (428.925667ms)
✔ N10: fake conformance (34.033601ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (529.657483ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (313.788867ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (391.601448ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (484.338398ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (489.422664ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (497.122206ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (296.008524ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (467.081721ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (734.233914ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (533.918507ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (502.012851ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (205.405617ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (193.442972ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (185.960475ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (195.282092ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (203.424206ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 63077.020588
✖ failing tests:
test at packages/conversation/tests/claim.test.mjs:716:1
✖ no shim from this file's tests is left running for the after hook (5046.252437ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3407906,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-noprobe/chat03-Q8oINH/f55/sock/shim-c32a2893a970.sock',
+ unit: 'mosaic-chat-c32a2893a9706deb3bec82ca0'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs:717:10)
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: [ { pid: 3407906, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-noprobe/chat03-Q8oINH/f55/sock/shim-c32a2893a970.sock', unit: 'mosaic-chat-c32a2893a9706deb3bec82ca0' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,200 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (198.698815ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (32.862657ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.889047ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (485.604813ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (365.231889ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (232.065585ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (181.538597ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (183.821556ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.610861ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11633.757353ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (38168.11999ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (367.150641ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (145.663175ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (456.48183ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (316.350227ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (392.363184ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (115.172256ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (242.361847ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (305.279805ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (867.981978ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (411.049411ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (107.290437ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (79.610014ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (5.001315ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.182592ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.108134ms)
✖ no shim from this file's tests is left running for the after hook (5069.458005ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3088.297138ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (471.125148ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2756.987349ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5023.712968ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2554.275667ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2735.958459ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2522.163222ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5637.374272ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (825.150571ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (925.855507ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (799.307491ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (1542.680606ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (552.967583ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (546.085838ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3285.771371ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (24.867ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (376.020519ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (349.317488ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (61.467027ms)
✔ no shim from this file's tests is left running for the after hook (26.090499ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (199.663758ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (304.621318ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (209.329704ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (270.026975ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (189.943314ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.423509ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (188.105029ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (284.880872ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (192.41889ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (153.892727ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (189.945342ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (194.689916ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.931016ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (196.780721ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (213.861585ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (237.075778ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (208.338445ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.442794ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.300169ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.32973ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.511474ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.938535ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.47667ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (4.817998ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.431762ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.365261ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.121626ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (229.95575ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (244.144747ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (419.408382ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (768.597537ms)
✔ H4: self-takeover is refused (178.373354ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (389.61586ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (975.461449ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (248.747123ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (665.043422ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (310.63687ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (194.775379ms)
✔ H13: a retry with the same request ID and different text is refused (191.465442ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (547.019679ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (290.665609ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (189.887032ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (933.090386ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (523.920195ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (981.477342ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (218.106746ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (351.898454ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.598431ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (694.089088ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (514.270031ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (939.240048ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3072.076081ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (653.420449ms)
✔ no shim from this file's tests is left running for the after hook (26.494042ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.54234ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.548775ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.498421ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.194536ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.447354ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.786317ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.744188ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.141692ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.091082ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.959532ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.701716ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.696632ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.324616ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.794386ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.567147ms)
✔ F9: registrations never add or redirect a root (1.458632ms)
✔ F10: a header cwd naming another project is refused (3.704001ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.647529ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.690779ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.971159ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.435078ms)
✔ F13: compaction is a marker in place, then the retained content (0.776037ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (509.154104ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.140956ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.694451ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.573043ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.247235ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.171709ms)
✔ every page and cursor is a valid CHAT-01 record (758.079284ms)
✔ the engine pin holds for the installed package (1.62462ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (247.368358ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (542.398677ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (238.906024ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (592.557974ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (408.954193ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (218.537145ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.789466ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (327.144677ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (187.305082ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (277.516328ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (443.666689ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1846.380562ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (194.887956ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (280.229511ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (184.022735ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (176.418657ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (397.733238ms)
✔ N10: fake conformance (31.9138ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (473.51531ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (281.37194ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (327.373894ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (519.646809ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (519.289096ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (413.910759ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (216.468282ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (319.436286ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (673.393295ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (486.237526ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (671.744395ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (313.768091ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (182.117391ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (259.568701ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (270.08494ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (254.988226ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 63864.593514
✖ failing tests:
test at packages/conversation/tests/claim.test.mjs:716:1
✖ no shim from this file's tests is left running for the after hook (5069.458005ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3447135,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-now13/chat03-49mRK9/f97/sock/shim-caf8d6f42417.sock',
+ unit: 'mosaic-chat-caf8d6f4241771cc4dd83db5c'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs:717:10)
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: [ { pid: 3447135, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-now13/chat03-49mRK9/f97/sock/shim-caf8d6f42417.sock', unit: 'mosaic-chat-caf8d6f4241771cc4dd83db5c' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,173 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (207.271804ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (27.378503ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.734201ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (401.463097ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (424.149463ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (260.321182ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (216.303228ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (198.475187ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.530333ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9580.246552ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (32490.502812ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (265.204197ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (146.055374ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (608.687771ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (283.134728ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (348.792391ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (110.258855ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (168.831108ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (293.044336ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1011.928942ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (192.216459ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (95.914112ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (70.729115ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.126228ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.074734ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.743051ms)
✔ no shim from this file's tests is left running for the after hook (26.734544ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2951.343801ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (430.822757ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2740.61561ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4802.817192ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2465.610533ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2710.911294ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2365.599726ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5208.374708ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (705.036427ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (556.452572ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (678.820054ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (675.592705ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (324.575793ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (516.070431ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3248.044103ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (22.751721ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (316.43844ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (283.795513ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (83.41128ms)
✔ no shim from this file's tests is left running for the after hook (27.163484ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (197.270317ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (196.984698ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (242.28257ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (264.905841ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (251.57943ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.428407ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (263.885551ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (305.700471ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (223.918645ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (195.860354ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (214.150476ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (204.109833ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.909702ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (204.12687ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (225.290366ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (250.520935ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (206.142267ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.41733ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.27429ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.37043ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.502339ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.943287ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.504745ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.717556ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.483407ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.329417ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.099683ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (245.471689ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (246.021579ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (335.482637ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (923.349311ms)
✔ H4: self-takeover is refused (191.466141ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (456.147402ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1017.207863ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (236.676987ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (661.959995ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (296.013005ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (265.514914ms)
✔ H13: a retry with the same request ID and different text is refused (216.459394ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (771.292898ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (367.682417ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (228.139489ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (891.312509ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (307.782218ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (778.911906ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (198.565122ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (341.100834ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.610067ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (633.054053ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (535.933625ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (661.804814ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2929.74862ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (669.043564ms)
✔ no shim from this file's tests is left running for the after hook (27.421304ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.900029ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.636176ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.750661ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.40655ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.804873ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.858031ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.844388ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.246411ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.458235ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.565903ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.341684ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.313517ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.109439ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.687318ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.005059ms)
✔ F9: registrations never add or redirect a root (1.84741ms)
✔ F10: a header cwd naming another project is refused (4.18711ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.831652ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.297263ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.129671ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.696325ms)
✔ F13: compaction is a marker in place, then the retained content (0.866729ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (519.860154ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.62088ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.530826ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.372473ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.106265ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.06523ms)
✔ every page and cursor is a valid CHAT-01 record (774.932604ms)
✔ the engine pin holds for the installed package (1.897167ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (278.377573ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (523.405456ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (242.204143ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (519.439509ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (414.693165ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (312.944285ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.937563ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (401.944364ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (205.60076ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (303.758094ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (463.80476ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1950.729013ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (232.903168ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (363.906033ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (282.17414ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (265.639301ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (474.858611ms)
✔ N10: fake conformance (32.677774ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (447.029703ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (286.460188ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (290.431139ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (366.969251ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (380.832064ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (354.439631ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (199.111062ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (272.807596ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (676.108776ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (541.840477ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (492.00608ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (229.057693ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (175.273367ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (193.822882ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (216.01477ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (187.571295ms)
ℹ tests 165
ℹ suites 0
ℹ pass 165
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 49363.024117
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# run.sh <mutant>: mutate, run the conversation suite, list and kill shims left under TMPDIR, restore from backups.
m=$1
export TMPDIR=~/darkwing-scratch/r47a/tmp/mut-$m DOCKER_HOST=unix:///nonexistent.sock
mkdir -p $TMPDIR
M=~/darkwing-scratch/r47a/mut; cd ~/darkwing-scratch/r47a/wt
files=$(python3 -I $M/mutate.py $m --files)
for f in $files; do cp -p $f $f.mutbak; done
if python3 -I $M/mutate.py $m > /dev/null; then
node --test "packages/conversation/tests/*.test.mjs" > $M/$m-conversation.txt 2>&1; e=$?
else e=mutate-failed; fi
left=$(ps -eo pid=,args= | grep -F "$TMPDIR/" | grep -F /shim.mjs | grep -v grep)
n=$(printf '%s' "$left" | grep -c .)
echo "$left" > $M/$m-shims-left.txt
printf '%s\n' "$left" | while read pid rest; do
[ -n "$pid" ] || continue
u=$(tr '/' '\n' < /proc/$pid/cgroup | grep '^mosaic-chat-.*\.scope$')
[ -n "$u" ] && systemctl --user kill --signal=SIGKILL "$u"
kill -9 $pid 2>/dev/null
done
for f in $files; do mv $f.mutbak $f; done
echo "$m rc $e $(grep -E '^ℹ (pass|fail)' $M/$m-conversation.txt | tr '\n' ' ') shims-left $n" >> $M/summary.txt
@@ -0,0 +1,13 @@
Mr rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
noprobe rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
now13 rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
afterleak rc 1 ℹ pass 164 ℹ fail 2 shims-left 1
sweeponly rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
sweeppid rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
k19leak rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
h23noreap rc 0 ℹ pass 165 ℹ fail 0 shims-left 0
reapnosweep rc 0 ℹ pass 165 ℹ fail 0 shims-left 0
DONE
manifest rc=0
h22noreap rc 1 ℹ pass 164 ℹ fail 1 shims-left 0
manifest rc=0 after h22noreap (run after DONE)
@@ -0,0 +1,200 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (219.22162ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (23.765285ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.835106ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (677.055327ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (565.256821ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (257.310579ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (239.136175ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (218.373694ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.590114ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (12029.61787ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (30878.301788ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (287.664713ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (155.736773ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (452.81341ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (315.312236ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (379.01671ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (118.606186ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (211.612835ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (279.515244ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (785.941592ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (199.611313ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (95.335199ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (77.381194ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.361384ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.142543ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.938508ms)
✖ no shim from this file's tests is left running for the after hook (5019.603223ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3200.252717ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (529.274581ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2868.634419ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5110.98429ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2541.203565ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2475.547415ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2361.050725ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (6461.358245ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (713.270412ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (564.741213ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (483.075851ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (794.671937ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (426.842821ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (569.619555ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3261.74847ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (27.269794ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (531.307009ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (465.646205ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.674166ms)
✔ no shim from this file's tests is left running for the after hook (25.397912ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (229.034341ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (502.781612ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (216.97478ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (451.470365ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (222.111955ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.397399ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (227.363437ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (339.547516ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (242.128975ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (210.685516ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (242.777703ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (344.665889ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (9.896061ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (252.050993ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (254.617339ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (277.942168ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (235.804509ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.412249ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.261896ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.323508ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.476089ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.706168ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.384241ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (4.691081ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.356412ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.323979ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.096363ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (225.795171ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (269.285437ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (611.712432ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (1040.161233ms)
✔ H4: self-takeover is refused (223.766709ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (483.992838ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1331.586166ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (281.034529ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (721.030469ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (365.24181ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (232.315138ms)
✔ H13: a retry with the same request ID and different text is refused (218.980022ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (686.025613ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (307.533843ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (232.615491ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (923.383092ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (539.323132ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (838.31859ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (229.489054ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (396.824559ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.653646ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (678.692377ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (625.288367ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (722.611686ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2839.482157ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (610.170391ms)
✔ no shim from this file's tests is left running for the after hook (24.228875ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.463565ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.693891ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.733322ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.531828ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.344865ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.071245ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.803497ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.251514ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.411618ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.329635ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.675201ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (6.025147ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.7227ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.835583ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.728641ms)
✔ F9: registrations never add or redirect a root (1.742691ms)
✔ F10: a header cwd naming another project is refused (3.9573ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.759548ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.09653ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.228863ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.408581ms)
✔ F13: compaction is a marker in place, then the retained content (0.897507ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (534.197443ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.583271ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.532858ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.342871ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.15181ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.03718ms)
✔ every page and cursor is a valid CHAT-01 record (779.363124ms)
✔ the engine pin holds for the installed package (1.849074ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (279.715201ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (547.522098ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (242.72555ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (812.948913ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (615.318151ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (266.66633ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.877037ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (412.712916ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (226.935176ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (332.816958ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (652.278512ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1943.118139ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (222.075483ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (324.648835ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (237.783638ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (264.056599ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (389.449033ms)
✔ N10: fake conformance (32.857365ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (413.601451ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (310.062237ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (294.630345ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (484.253844ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (496.213776ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (381.020463ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (207.059927ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (440.374037ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (649.883294ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (651.981202ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (489.606583ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (139.447168ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (173.130328ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (184.996545ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (207.728707ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (239.825509ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53766.897467
✖ failing tests:
test at packages/conversation/tests/claim.test.mjs:715:1
✖ no shim from this file's tests is left running for the after hook (5019.603223ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3491796,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-sweeponly/chat03-NvSlTB/f55/sock/shim-c57cf2b7dc43.sock',
+ unit: 'mosaic-chat-c57cf2b7dc4384ee284c7c8ca'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs:716:10)
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: [ { pid: 3491796, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-sweeponly/chat03-NvSlTB/f55/sock/shim-c57cf2b7dc43.sock', unit: 'mosaic-chat-c57cf2b7dc4384ee284c7c8ca' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,200 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (212.307304ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.241548ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.690237ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (365.735842ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (476.656649ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (252.219246ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (203.752506ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (218.059827ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.827711ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (12986.540113ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (29667.327918ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (263.096526ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (153.684222ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (438.623192ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (292.216779ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (354.209654ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (99.843498ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (199.922328ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (270.3889ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (785.105243ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (197.740901ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (88.807427ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (69.375455ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.533954ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.057677ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.755071ms)
✖ no shim from this file's tests is left running for the after hook (5015.591033ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3012.37604ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (456.752613ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2853.357608ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5101.754986ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2510.116268ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2507.829556ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2385.107361ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5096.425544ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (748.270129ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (698.688142ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (461.090943ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (764.265641ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (368.273775ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (585.010913ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3303.227292ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (44.55575ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (340.095364ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (407.45662ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (65.922828ms)
✔ no shim from this file's tests is left running for the after hook (26.780264ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (203.806154ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (198.186853ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (199.091556ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (346.518529ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (190.366735ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.478363ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (196.877259ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (303.887167ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (250.230797ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (190.267876ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (202.246951ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (204.796413ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.997429ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (206.23248ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (215.165122ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (232.091292ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (197.742321ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.433064ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.264916ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.33873ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.482271ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.503364ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.508488ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.194845ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.502874ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.374229ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.141727ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (244.73443ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (251.027442ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (295.533573ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (874.357109ms)
✔ H4: self-takeover is refused (230.948664ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (432.850202ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1003.554755ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (203.401943ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (713.216897ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (327.609666ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (187.90498ms)
✔ H13: a retry with the same request ID and different text is refused (180.014369ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (617.363478ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (269.250448ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (200.985765ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (893.863913ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (361.590816ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (772.788917ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (191.089022ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (339.528421ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.617666ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (662.309994ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (1578.967408ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (841.811267ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2893.408865ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (609.408041ms)
✔ no shim from this file's tests is left running for the after hook (24.887141ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.826388ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.874992ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.088607ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.046621ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.89486ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (3.832698ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (2.351568ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.059196ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.549088ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.912965ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (6.192415ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.499ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.619517ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (12.775709ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.547278ms)
✔ F9: registrations never add or redirect a root (2.661461ms)
✔ F10: a header cwd naming another project is refused (5.443268ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.748576ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.475394ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.950489ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.66476ms)
✔ F13: compaction is a marker in place, then the retained content (0.810283ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (568.348502ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.675357ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.769068ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.773876ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.250822ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.338683ms)
✔ every page and cursor is a valid CHAT-01 record (878.978932ms)
✔ the engine pin holds for the installed package (1.990805ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (299.382164ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (600.196612ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (293.379255ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (478.752914ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (496.518825ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (229.675189ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.369485ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (345.76914ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (209.867289ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (310.103199ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (449.582349ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1873.348979ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (145.516554ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (281.565014ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (202.855278ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (238.854877ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (366.704339ms)
✔ N10: fake conformance (31.268588ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (387.555351ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (254.221566ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (318.654843ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (392.533184ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (375.602357ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (370.59127ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (197.627792ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (353.031336ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (687.713946ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (406.944589ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (1522.272602ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (237.477898ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (209.176383ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (176.405994ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (146.513631ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (221.956939ms)
ℹ tests 165
ℹ suites 0
ℹ pass 164
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 52878.90005
✖ failing tests:
test at packages/conversation/tests/claim.test.mjs:715:1
✖ no shim from this file's tests is left running for the after hook (5015.591033ms)
AssertionError [ERR_ASSERTION]: a test left its shim running
+ actual - expected
+ [
+ {
+ pid: 3524005,
+ socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-sweeppid/chat03-LcZocr/f55/sock/shim-c0dce842c2ea.sock',
+ unit: 'mosaic-chat-c0dce842c2ea498500eca8f92'
+ }
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r47a/wt/packages/conversation/tests/claim.test.mjs:716:10)
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: [ { pid: 3524005, socket: '/home/jwoltje/darkwing-scratch/r47a/tmp/mut-sweeppid/chat03-LcZocr/f55/sock/shim-c0dce842c2ea.sock', unit: 'mosaic-chat-c0dce842c2ea498500eca8f92' } ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,173 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (748.083419ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (48.776016ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (27.06872ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (421.457932ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (402.390693ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (254.045709ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (244.400915ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (239.716607ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.325186ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (13356.104987ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (36068.863466ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (252.623185ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (148.149392ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (630.377836ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (286.783672ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (347.24802ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (102.897217ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (220.305866ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (418.974636ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (932.402989ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (204.140858ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (92.498337ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (78.471097ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.439765ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.197349ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.813056ms)
✔ no shim from this file's tests is left running for the after hook (25.234145ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3507.986737ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (488.26111ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2807.697949ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5077.47375ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2577.773206ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2709.866313ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2448.965499ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5820.759171ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (729.781747ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (645.129116ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (652.019615ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (789.185088ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (367.180176ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (806.040921ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3273.574724ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (24.586649ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (299.433157ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (299.231036ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (81.16899ms)
✔ no shim from this file's tests is left running for the after hook (25.931375ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (203.436663ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (773.840378ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (268.793695ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (331.406567ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (220.921848ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.427235ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (205.441943ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (310.116384ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (271.294599ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (201.090126ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (231.32039ms)
✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (218.02235ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.602383ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (224.057707ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (236.338343ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (281.785722ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (228.267016ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.418138ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.280314ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.306187ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.48246ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.948332ms)
✔ terminal: a hold inside held input holds again, and once it is drained later input and Ctrl-C still reach the terminal (Darkwing note 1 on #1522) (0.436041ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.664433ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.323378ms)
✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.399489ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.102825ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (221.258318ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (836.995174ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (345.379527ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (912.356131ms)
✔ H4: self-takeover is refused (234.984573ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (468.136324ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1141.024711ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (236.737014ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (1160.793929ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (317.507653ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (222.364545ms)
✔ H13: a retry with the same request ID and different text is refused (215.785684ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (633.428602ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (290.244169ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (221.311078ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (920.773309ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (667.241115ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (1249.039812ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (331.549187ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (461.162549ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.655085ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (753.02375ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (619.748198ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (791.966316ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2953.46659ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (821.845284ms)
✔ no shim from this file's tests is left running for the after hook (25.659792ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (8.997065ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.836337ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.869323ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.785801ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.514939ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.08467ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.109708ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.43512ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.918335ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.373201ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.010585ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.206126ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.340925ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.045792ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.879664ms)
✔ F9: registrations never add or redirect a root (1.864646ms)
✔ F10: a header cwd naming another project is refused (4.700498ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.909364ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (6.844885ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.439724ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.604906ms)
✔ F13: compaction is a marker in place, then the retained content (0.802607ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (522.527587ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.380359ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.847291ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.816787ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.358511ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.385576ms)
✔ every page and cursor is a valid CHAT-01 record (863.558219ms)
✔ the engine pin holds for the installed package (2.284265ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (262.803824ms)
✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (537.60608ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (331.695583ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (1083.153523ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (535.341366ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (248.458287ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.767648ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (386.548344ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (250.152615ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (324.635181ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (507.783689ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1931.729518ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (221.397946ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (324.497019ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (148.971188ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (261.283024ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (441.385924ms)
✔ N10: fake conformance (32.218012ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (429.582597ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (273.423146ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (303.722538ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (395.259681ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (417.929598ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (669.815407ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (341.540953ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (551.736315ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (1329.575149ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (456.102377ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (595.331647ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (285.686961ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (232.628891ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (187.264585ms)
✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (212.475028ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (229.935301ms)
ℹ tests 165
ℹ suites 0
ℹ pass 165
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 59084.73944
@@ -0,0 +1,31 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3737.296989ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3966.21881ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4260.503082ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (273.228743ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (234.452668ms)
✔ bus refusals map to statuses and never carry a path or a stack (76.516425ms)
✔ humanCall speaks the human transport protocol without blocking the server (2784.361067ms)
✔ busReader takes --business, else the live bus host, else refuses (149.074355ms)
✔ humanCall kills a transport that runs past its timeout (528.127044ms)
✔ serve refuses a --business value that is not a business id (203.925382ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3486.138769ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1870.194644ms)
✔ conversation view: a newer session with no readable history keeps the marker (1117.230752ms)
✔ conversation view: seats without history say so and offer no reply (870.280067ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3625.064473ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53317.26966ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3752.067649ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22335.011066ms)
✔ loopback host and board origin fail closed (6.498143ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (102.695568ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (94.391046ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1768.953308ms)
ℹ tests 22
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53693.094645
@@ -0,0 +1,13 @@
node-conversation exit=0 ℹ pass 165 ℹ fail 0 shims-left=0
node-webui exit=0 ℹ pass 22 ℹ fail 0 shims-left=0
test-auth exit=0 selftest: 15 passed, 0 failed
test-conductor exit=0 selftest: 17 passed, 0 failed
test-config exit=0 selftest: 24 passed, 0 failed
test-discord exit=0 discord suite: 66 passed, 0 failed
test-extension-package exit=0 extension package selftest: 18 passed, 0 failed
test-foundation exit=0 selftest: 44 passed, 0 failed
test-queue exit=0 queue suite: 27 passed, 0 failed
test-release exit=0 selftest: 4 passed, 0 failed
test-task exit=1 selftest: 26 passed, 2 failed
shims-left-end=0
DONE
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,55 @@
Note: switching to '21e0f1a31c559dfe4031e6de635ffb11d1a34606'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
Not currently on any branch.
nothing to commit, working tree clean
Note: switching to '21e0f1a31c559dfe4031e6de635ffb11d1a34606'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 3331064 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,35 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 148
ℹ pass 148
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/home/jwoltje/darkwing-scratch/r47a/wt) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,16 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
@@ -0,0 +1,7 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
skip state-machine cases (docker daemon unavailable)
selftest: 4 passed, 0 failed
@@ -0,0 +1,33 @@
OK valid task validates (exit 0)
OK unknown task key exits 2 (exit 2)
OK unsupported taskVersion exits 2 (exit 2)
OK invalid task id exits 2 (exit 2)
OK empty prompt exits 2 (exit 2)
OK NUL in expectExact exits 2 (exit 2)
OK out-of-range timeout exits 2 (exit 2)
OK missing mission file exits 4 (exit 4)
OK task with valid mission validates (exit 0)
OK invalid mission exits 2 (exit 2)
OK validate missing task exits 4 (exit 4)
OK validation does not modify the task file
OK prune dry-run exits 0 (exit 0)
OK dry-run deleted nothing
OK prune --keep=2 --yes removes oldest (exit 0)
OK kept exactly 2 newest runs
OK newest run kept, oldest pruned
OK append-only receipt written (3 entries)
OK sessions/workspaces untouched by prune
OK prune with invalid keep exits 4 (exit 4)
skip adapter seam cases (docker daemon unavailable)
skip workspace/capability cases (docker daemon unavailable)
skip live task cases (docker unavailable)
OK onboard without name exits 4 (non-interactive) (exit 4)
OK onboard --name renders profile (exit 0)
OK profile written
OK canon structure: required filled, optional placeholdered
OK canon sections present
FAIL user recall run succeeds (exit 1)
FAIL recalled user name (response: )
OK no agent identity on headless run
selftest: 26 passed, 2 failed
@@ -0,0 +1,158 @@
# Row 47, S5 follow-up (Mr flows test and the shim leak), round 1 review (Darkwing)
Issue #1533, request comment 27036, notes comment 27037, queue revs 277
and 278 (`10351f6a`). Brief: `docs/plans/2026-10-10_s5-follow-up-and-hygiene.md`
(blob `24ceb88c`), section "S5 follow-up". Base `21e0f1a3`. Candidate
manifest sha256
`0587e393746e5cc6bb31e06b94461661fc7679f6f5fa6a72835c60b286a6055f`,
6 files: five test files in `packages/conversation/tests/` and Dewey's
`evidence.md`. No `src/` change.
Verdict: **approve**, comment 27043. The new flows test is the shape of my row 40 note 1
probe in both chunkings, and it's the only test Mr fails. The leak
diagnosis checks out against the code: the shim ignores SIGTERM, nothing in
`src/` sends `release`, and two claim tests left a force-stopped scope for
an `after` hook whose kill results nobody checked. Each test now reaps its own
scope, every scope-launching file ends with a check that names a leak, and
the `after` hook fails the file if a shim survives the sweep. Every leak
mutant I tried in claim, cohort and races is killed, and `afterleak`
reproduces the round 2 leak with the hook catching it. Two notes below,
neither blocking.
## Method
- A detached worktree at `21e0f1a3` with the six files copied from a
snapshot of the canonical tree, then `sha256sum -c`: 6 OK. Checked again
after the mutants: 6 OK (`r1/mut/manifest-after.txt`).
- I read the diff in full, `src/shim.mjs`, `ScopeLauncher` and
`forceStopCohort` in `src/cohort.mjs`, `#run` in `src/terminal.mjs`, and
W5, W13, K19, H22 and H23.
- 10 mutants (`r1/mut/mutate.py`, `run.sh`), each run on the whole
conversation suite with its own TMPDIR. After each run the runner lists any
shim left under that TMPDIR, SIGKILLs its `mosaic-chat-*` scope and the PID,
and restores the files from backup copies.
- No shim was running before the gate, after it, or after any mutant other
than `afterleak` (killed by the runner, see below). After all runs
`systemctl --user list-units --all 'mosaic-chat-*'` lists nothing.
Node is v26.8.1, `TMPDIR=~/darkwing-scratch/r47a/tmp`. `gate.sh` set
`DOCKER_HOST=unix:///nonexistent.sock`.
## Suites
| Suite | Result |
|---|---|
| conversation | 165/0 |
| webui | 22/0 |
| test-auth | 15/0 |
| test-conductor | 17/0 |
| test-config | 24/0 |
| test-discord | 66/0 |
| test-extension-package | 18/0 |
| test-foundation | 44/0 |
| test-queue | 27/0 |
| test-release | 4/0 without Docker, 14/0 with it (`test-release-docker.txt`) |
| test-task | 26/2 |
The two `test-task` failures are "user recall run succeeds" and "recalled
user name", the live worker check that needs Docker and a model call. They
fail the same way in my row 41 gates. Dewey's run reports 98/0, so it ran
the Docker cases; I didn't.
## Mr
The test types Ctrl-T, Ctrl-O inside the text held behind it, `hi` Enter,
then `yo` Enter and Ctrl-C, once as three chunks and once as one. It
asserts `sent` is `["hi"]`, then `held` is null, then `yo` goes through and
Ctrl-C quits. That's my `probe/stacked-later.mjs` from row 40, plus the
single-chunk case, where the hold and the stacked hold come from one
`#parse`. Under Mr (`holds = false;` at `terminal.mjs:158`) the suite is
164/1 and the one failure is this test (`r1/mut/Mr-conversation.txt`).
## The leak
Dewey's reading matches the code:
- `shim.mjs` ignores SIGTERM and SIGHUP and exits only on `release` once the
engine cgroup is empty.
- The only shim ops `src/` sends are `hello`, `term`, `events`, `freeze`,
`members` and `kill` (`cohort.mjs:115-192`). So a force-stopped scope
stays up with its shim until something SIGKILLs the unit.
- W5's probe and W13 force-stop a scope and left it to the `after` hook,
whose `systemctl --user kill` and `stop` results were never checked.
The evidence names f55, the W5 probe fixture, as Filbert's leaked shim. My
`afterleak` run leaked the same fixture (`f55/sock/shim-cede1ee324e5.sock`).
Dewey says what the journal can't show, which is why the `after`
kill missed in Filbert's run. The fix doesn't depend on knowing: the tests
reap right away, and the sweep kills by PID whatever `systemctl` does.
The new helpers read `/proc/<pid>/cmdline` for `node <...>/shim.mjs --socket
<path under tmp>`, which is exactly `ScopeLauncher`'s argv
(`cohort.mjs:105`). `tmp` is per test process, so each file sees only its
own shims, and a parallel gate from another seat isn't touched. `noprobe`
failing proves the detector finds a real shim.
K19's `finally` used to assert on `kill` before sending `release`, so a
failed kill left the scope. It now sends `release` only after a good kill,
SIGKILLs the scope if either fails, and asserts afterwards.
## Mutants
| Mutant | Change | Conversation | Result |
|---|---|---|---|
| Mr | `holds = false;` after re-parsing held text | 164/1 | killed by the new flows test |
| noprobe | W5 doesn't reap its probe | 164/1 | killed: claim's "no shim ... left running" |
| now13 | W13 doesn't reap its fixture | 164/1 | killed, same test |
| afterleak | noprobe, and `after` doesn't reap or sweep | 164/2 | killed: the same test and claim's `after` hook; 1 shim left, killed by the runner |
| sweeponly | noprobe, and `after` sweeps but doesn't reap | 164/1 | killed by the last test; the sweep alone cleans up, no shim left |
| sweeppid | sweeponly, and `killShims` skips the scope kill | 164/1 | same; the PID kill alone is enough |
| k19leak | K19's `finally` sends no kill and no release | 164/1 | killed: cohort's "no shim ... left running" |
| h22noreap | H22 (force stop, then relaunch in a scope) doesn't reap | 164/1 | killed: races' "no shim ... left running" |
| h23noreap | H23 doesn't reap | 165/0 | equivalent: H23 runs on `pgroup`, no shim |
| reapnosweep | `reap()` without its `killShims(fx.base)` | 165/0 | survives, expected: it's a backstop for a missed `systemctl` |
`afterleak` is the round 2 leak on purpose. Its hook failure names the shim
(`r1/mut/afterleak-conversation.txt:205`), and node:test marks the file
failed:
```
AssertionError [ERR_ASSERTION]: no shim outlives this file (#1533)
+ pid: 3459119,
+ socket: '.../tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock',
+ unit: 'mosaic-chat-cede1ee324e5b263ef2671152'
```
So all three scope-launching files have their final check killed by a leak
mutant, which Dewey's table showed only for claim.
## Notes (not blocking)
1. `liveShims` takes the first cgroup segment ending in `.scope` as the
shim's unit, and `killShims` SIGKILLs that whole unit. Today every shim
comes from `ScopeLauncher`, so that's the `mosaic-chat-*` scope. A future
test that spawned `shim.mjs` directly from a shell inside a terminal's
`app-*.scope` would get that terminal's scope killed. Matching
`/^mosaic-chat-.*\.scope$/`, which is what my runner does, costs one
regex. Worth folding into the next change to `harness.mjs`.
2. The brief asks for the Mr output in the packet. `evidence.md` has the
table and the `afterleak` excerpt; the outputs stay in
`~/dewey-scratch`. Mine are in `r1/mut/` if Sage wants them on file.
## Finding for Sage
I agree with Dewey's out-of-scope finding and the recommended follow-up. In
a running controller every force stop leaves a `mosaic-chat-*.scope` with
an idle node shim, until the unit is killed by hand or the user manager
restarts. The fix is `release` after a proven force stop (and on close of a
stopped binding), with a test that the unit is gone. That's `cohort.mjs` or
`controller.mjs` work, outside this brief.
## Files
- `r1/candidate-manifest.sha256`, `r1/files.txt`: copies of Dewey's.
- `r1/gate.sh`, `r1/out/`: suite runs, `summary.txt` with a shim count
after each node suite and at the end.
- `r1/mut/`: mutant definitions, runner, per-mutant output and shims-left
lists, `summary.txt`, and the manifest check after the runs. `h22noreap`
ran after the first batch, so its line follows `DONE`.