docs(review): row 40 round 3 review packet, approve (darkwing)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-09 21:59:58 -05:00
co-authored by Claude Opus 5.5
parent 09bcd1a9d0
commit 67630cc41f
56 changed files with 4538 additions and 0 deletions
@@ -0,0 +1,27 @@
cef660ecd9389500cfcd1e4d16ca7e14a204a158526d747d75ccd6af6b15e79e agents/dewey/work/wui/S5-GATE-E.md
416a6014762b06fb6dd463bc90a4f28316dd64e8743ac50aff8b1000c551d5ec agents/dewey/work/wui/SLICE1-VIEWS.md
1179401b3252a7e8f54e5bf2f0d7c460d83596cf7140c79cbb38258ec84a7193 agents/dewey/work/wui/TASKS.md
40034b67f4cc4cd007eff36502ae723882688feffa8a5856981950c3218c611c agents/dewey/work/wui/evidence/s5-mutants.md
601e39d6f203f969c84dd9c5d23e28b191071c71f2a35374ebea18d2683e243a docs/plans/DEFERRED.md
c38191d302da8757a3329abed5e443d06d8eb7b09a0f583d67827ffc1a74e838 packages/conversation/README.md
38621d505f6ffd859e37c4309f7a2d3a3ee21a53d1ba41b8ff9ca29250a6dc37 packages/conversation/src/cohort.mjs
26f8fa2c7caf794e6224802b8e5d8882419d0bda725d2ab30f0a058a678a57b9 packages/conversation/src/controller.mjs
a06468434e7bf3a3dfc3c5ea05b8c21a1510f8fa0c8eaa6e9908b970fa98367e packages/conversation/src/pi-pin.mjs
c24d28a073659640b8b027eb12ad150ba895c3001091b892ecef2ec1da40d5cd packages/conversation/src/terminal.mjs
a555d3c9394a911cc1e33191eb9112f0a3f0aca8c78ce6adc888a2d40d4a2ffb packages/conversation/tests/cohort.test.mjs
6310ea0163c5fcd3f1cbd15e4433ba9c1897e9649ceed1e8e169ed8fb884ad36 packages/conversation/tests/ctrl-child.mjs
881121aadcf879a56c84722eec7726b0a71160c58bf99056758dab3d285c514d packages/conversation/tests/fake-pi.mjs
0c2647b62c540bee7164686ea028c5a48e2d570af967a5b3dfa271893a51d6d8 packages/conversation/tests/flows.test.mjs
28e66a9cc9876660418f4fe171ec51639313ce2ca9675d2e0fdcf7b9c8b5e446 packages/conversation/tests/races.test.mjs
b7b5fa60374a55270c5584fff5c97ae2c25ad2056a5569c285c09e78e35752ce packages/conversation/tests/smoke.test.mjs
9d0078fa7fa8335d336130a5f0b44f141448e9eb3de59efab1909e50e0243bdb packages/conversation/tests/turns.test.mjs
18ca9e95d8dbda1279fb96d6c38bac1142b6da9990b6cb36499c205882380b58 packages/webui/README.md
da2ac17666296b954141756fd687652c623560787f03e64b4e43d863b9bfda3d packages/webui/src/bus.mjs
06b35c24ef6e25aeeadeef66ffccf090378e5b51d13cf81b04801dba93407f06 packages/webui/src/cli.mjs
341a3669b3b45b91a724ea1bc338ab56fda18491db55ac96123f252cb93a6935 packages/webui/src/public/bus.css
794c4604dc2b9d0dab5f32e8b44358f3a0f0d7e6682a2cbf393c0f7b42fbb1b8 packages/webui/src/public/bus.js
b27d4fbec45f716f2cafee10d8e3abf3be6d2f1701a4f55f52f70dcc9f04fcc4 packages/webui/src/public/index.html
2b60c4bca11b3c53d4affb22ce482c9fb0db6b213ffec298473fe899a9e5d026 packages/webui/src/serve.mjs
18b1bd786a1e74d554d3bc3de4e0c142203fb43fcd860560bfd6263aa9e6ed11 packages/webui/tests/browser.test.mjs
416df2a9a2feca4f1cf9393d092f682eef06850769675b7bb28fc73c05e77010 packages/webui/tests/bus-browser.test.mjs
2af65f0d48ec7632e12a22aeb5c84ae4c5c183fbb08e282fe557bafc2764b8b5 packages/webui/tests/bus.test.mjs
@@ -0,0 +1,27 @@
agents/dewey/work/wui/S5-GATE-E.md
agents/dewey/work/wui/SLICE1-VIEWS.md
agents/dewey/work/wui/TASKS.md
agents/dewey/work/wui/evidence/s5-mutants.md
docs/plans/DEFERRED.md
packages/conversation/README.md
packages/conversation/src/cohort.mjs
packages/conversation/src/controller.mjs
packages/conversation/src/pi-pin.mjs
packages/conversation/src/terminal.mjs
packages/conversation/tests/cohort.test.mjs
packages/conversation/tests/ctrl-child.mjs
packages/conversation/tests/fake-pi.mjs
packages/conversation/tests/flows.test.mjs
packages/conversation/tests/races.test.mjs
packages/conversation/tests/smoke.test.mjs
packages/conversation/tests/turns.test.mjs
packages/webui/README.md
packages/webui/src/bus.mjs
packages/webui/src/cli.mjs
packages/webui/src/public/bus.css
packages/webui/src/public/bus.js
packages/webui/src/public/index.html
packages/webui/src/serve.mjs
packages/webui/tests/browser.test.mjs
packages/webui/tests/bus-browser.test.mjs
packages/webui/tests/bus.test.mjs
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
cd ~/darkwing-scratch/r40c/wt
export TMPDIR=~/darkwing-scratch/tmp DOCKER_HOST=unix:///nonexistent.sock
O=../out; : > $O/summary.txt
for p in webui conversation control-board; do
s=$(date +%s); node --test "packages/$p/tests/*.test.mjs" > $O/node-$p.txt 2>&1; rc=$?
echo "node-$p rc=$rc $(grep -E '^ℹ (pass|fail)' $O/node-$p.txt | tr '\n' ' ') $(( $(date +%s)-s ))s" >> $O/summary.txt
done
for t in scripts/test-*.sh; do n=$(basename $t .sh)
s=$(date +%s); bash $t > $O/$n.txt 2>&1; rc=$?
echo "$n rc=$rc $(tail -3 $O/$n.txt | tr '\n' ' ' | cut -c1-160) $(( $(date +%s)-s ))s" >> $O/summary.txt
done
echo DONE >> $O/summary.txt
@@ -0,0 +1,217 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (216.441574ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (31.875599ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.1636ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (398.226853ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (431.148668ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (255.421161ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (203.859835ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (189.380547ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.47328ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10658.422189ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (29094.518667ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (257.986498ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (144.519371ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (419.507196ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (269.990721ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (335.636052ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (101.44507ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (167.830095ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (263.44201ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (735.932032ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (185.197623ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (91.101347ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (72.861514ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (5.068487ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.183086ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.921935ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2582.412882ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (442.793822ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2490.392635ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4735.026818ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2516.965438ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2524.529235ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2423.615707ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5008.606841ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (834.615934ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (590.527352ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (470.106868ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (883.942401ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (600.536854ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (510.777691ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3243.612573ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (20.564288ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (403.358121ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (261.303651ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (83.132046ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (205.125888ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (237.662439ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (193.256941ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (296.792964ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (197.706648ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.764437ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (199.028361ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (309.122338ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (204.129171ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (187.55121ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (202.900433ms)
✔ 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.205289ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.821827ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (203.098874ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (223.800977ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (243.780629ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (179.554169ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.455622ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.28622ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.341292ms)
✔ 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.542432ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (29.771553ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.858419ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.454032ms)
✔ 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.409066ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.11515ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (253.046617ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (252.453788ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (324.3337ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (824.383631ms)
✔ H4: self-takeover is refused (188.25691ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (434.289017ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1021.1381ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (200.685223ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (568.772484ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (268.480302ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (123.162232ms)
✔ H13: a retry with the same request ID and different text is refused (210.520329ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (677.993442ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (310.300517ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (188.387822ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (831.32342ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (344.741631ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (925.51757ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (263.993116ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (368.041654ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.824721ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (724.841912ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (657.127416ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (724.045834ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3314.996803ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (644.794325ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.183873ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.942661ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.776179ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.589293ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.997313ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.777885ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.909513ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.305239ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.850601ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (6.288045ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (9.31438ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (8.403081ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (9.132707ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.314702ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.273148ms)
✔ F9: registrations never add or redirect a root (1.597532ms)
✔ F10: a header cwd naming another project is refused (3.49055ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.784618ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.135685ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.67693ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.298769ms)
✔ F13: compaction is a marker in place, then the retained content (0.729814ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (561.19935ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.557575ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.474582ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.22427ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.018767ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.906781ms)
✔ every page and cursor is a valid CHAT-01 record (898.237037ms)
✔ the engine pin holds for the installed package (1.959657ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (291.759948ms)
✖ 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) (301.082248ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (284.019352ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (520.523504ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (453.114059ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (226.986306ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.235113ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (357.217428ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (197.658689ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (295.541096ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (452.008968ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1897.868365ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (224.526949ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (330.348176ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (206.099525ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (229.941327ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (369.603ms)
✔ N10: fake conformance (33.768124ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (424.810937ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (264.795464ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (281.312756ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (361.68432ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (511.916133ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (456.508258ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (214.434669ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (353.218891ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (787.067711ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (655.375911ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (540.881647ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (245.403854ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (224.145425ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (198.898676ms)
✔ 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 (451.889987ms)
✔ 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 (190.712834ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 46482.899678
✖ failing tests:
test at packages/conversation/tests/smoke.test.mjs:168:1
✖ 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) (301.082248ms)
AssertionError [ERR_ASSERTION]: the project skill is not loaded under the seal
+ actual - expected
[
'llama',
+ 'skill:probe',
+ 'skill:amazon-bedrock',
+ 'skill:aws-ai-ml',
+ 'skill:aws-auth',
+ 'skill:aws-billing-and-cost-management',
+ 'skill:aws-blocks',
+ 'skill:aws-cdk',
+ 'skill:aws-cloudformation',
+ 'skill:aws-compute',
+ 'skill:aws-containers',
+ 'skill:aws-database',
+ 'skill:aws-deployment',
+ 'skill:aws-iam',
+ 'skill:aws-messaging-and-streaming',
+ 'skill:aws-networking',
+ 'skill:aws-observability',
+ 'skill:aws-sdk-js-v3-usage',
+ 'skill:aws-sdk-python-usage',
+ 'skill:aws-sdk-swift-usage',
+ 'skill:aws-security',
+ 'skill:aws-serverless',
+ 'skill:aws-storage',
+ 'skill:diagnose-crash',
+ 'skill:launch-with-aws',
+ 'skill:signing-in-to-aws'
]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/smoke.test.mjs:175:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: [ 'llama', 'skill:probe', 'skill:amazon-bedrock', 'skill:aws-ai-ml', 'skill:aws-auth', 'skill:aws-billing-and-cost-management', 'skill:aws-blocks', 'skill:aws-cdk', 'skill:aws-cloudformation', 'skill:aws-compute', 'skill:aws-containers', 'skill:aws-database', 'skill:aws-deployment', 'skill:aws-iam', 'skill:aws-messaging-and-streaming', 'skill:aws-networking', 'skill:aws-observability', 'skill:aws-sdk-js-v3-usage', 'skill:aws-sdk-python-usage', 'skill:aws-sdk-swift-usage', 'skill:aws-security', 'skill:aws-serverless', 'skill:aws-storage', 'skill:diagnose-crash', 'skill:launch-with-aws', 'skill:signing-in-to-aws' ],
expected: [ 'llama' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,185 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (217.412165ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (31.703231ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.12461ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (505.450925ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (461.25034ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (280.012337ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (225.527967ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (212.530538ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.322044ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9854.398835ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28398.947835ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (256.956387ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (147.663883ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (385.393198ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (271.767391ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (366.218557ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (106.680356ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (168.007025ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (260.512945ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (729.340992ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (184.649191ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (94.819727ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (70.318762ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.897217ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.096525ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.719853ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2607.723662ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (545.250489ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2562.093795ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4749.744555ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2573.562278ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2469.183245ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2445.984678ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4923.046568ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (654.03052ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (567.825644ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (509.381625ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (678.846359ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (357.652954ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (489.623533ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3373.217409ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (41.075074ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (324.22541ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (343.053381ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (81.642916ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (204.554136ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (199.696713ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (296.202561ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (373.234755ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (252.69287ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.467903ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (234.47642ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (317.483573ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (222.941472ms)
✔ 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.138251ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (214.663169ms)
✔ 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 (238.356521ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.98768ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (294.378718ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (251.865348ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (258.085991ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (201.06573ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.402653ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.278646ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.317569ms)
✔ 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.470947ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.503922ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.802151ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.39944ms)
✔ 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.384215ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.119746ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (219.850884ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (253.113848ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (334.34807ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (1009.888902ms)
✔ H4: self-takeover is refused (212.651704ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (459.24516ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1245.171331ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (211.079317ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (566.654958ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (291.178611ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (181.967842ms)
✔ H13: a retry with the same request ID and different text is refused (188.861053ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (549.356516ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (287.426249ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (200.444487ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (857.504193ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (373.700939ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (887.924023ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (237.241452ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (478.20367ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.624939ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (686.653321ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (532.060281ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (775.209066ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2925.165901ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (562.491533ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.270405ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.609092ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.753356ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.527188ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.431048ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.071325ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.827319ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.305484ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.904469ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.838439ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.06037ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (6.251335ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.983893ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.487498ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.66451ms)
✔ F9: registrations never add or redirect a root (1.687486ms)
✔ F10: a header cwd naming another project is refused (4.387969ms)
✔ F11: parentSession renders with a marker and the parent is never opened (1.074852ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (6.082696ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.794846ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.25014ms)
✔ F13: compaction is a marker in place, then the retained content (0.676562ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (548.131627ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.69177ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.467359ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.238703ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.04245ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.880023ms)
✔ every page and cursor is a valid CHAT-01 record (793.400268ms)
✔ the engine pin holds for the installed package (1.831292ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (271.760635ms)
✔ 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.750506ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (252.66582ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (475.738758ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (617.59465ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (291.571227ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.144547ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (392.153943ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (207.011ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (305.791264ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (569.435704ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1938.11055ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (191.350582ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (274.957818ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (180.110451ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (203.005388ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (397.085912ms)
✔ N10: fake conformance (32.298405ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (425.442611ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (275.621285ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (284.445041ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (392.028686ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (360.535077ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (529.453942ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (322.859168ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (418.962901ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (695.398228ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (477.457834ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (611.262487ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (253.000269ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (155.251972ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (196.68809ms)
✔ 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 (243.109589ms)
✖ 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 (270.55856ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 45166.946228
✖ failing tests:
test at packages/conversation/tests/turns.test.mjs:985:1
✖ 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 (270.55856ms)
AssertionError [ERR_ASSERTION]: Missing expected rejection.
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/turns.test.mjs:1034:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'rejects',
diff: 'simple'
}
@@ -0,0 +1,207 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (211.076637ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (34.207349ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.350714ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (414.053757ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (413.816783ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (225.192824ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (190.455968ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (216.545174ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.182804ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9349.717374ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28351.01757ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (256.953613ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (134.482324ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (366.779552ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (266.29821ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (336.266747ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (103.05527ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (173.635408ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (254.629668ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (730.562698ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (181.120304ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (87.413008ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (72.272604ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.992493ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.336254ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.40581ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2538.428577ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (425.950301ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2496.560824ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4715.35978ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2475.395956ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2511.840121ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2366.15874ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5083.172933ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (722.668318ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (582.353925ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (469.567284ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (783.414104ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (570.206646ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (674.671567ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3291.984739ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (38.627999ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (353.788737ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (310.116839ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.245166ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (205.344429ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (199.68857ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (253.141895ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (281.156323ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (199.570388ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.415289ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (184.576386ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (297.989924ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (209.960986ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (187.738631ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (208.903124ms)
✔ 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.831672ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.748839ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (200.471243ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (212.798977ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (241.329932ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (185.815007ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.399705ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.29171ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.289316ms)
✔ 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.489715ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.102266ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.743651ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.380971ms)
✔ 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.395925ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.114167ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (229.019645ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (251.438005ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (271.794692ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (843.897866ms)
✔ H4: self-takeover is refused (188.864508ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (450.483164ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1013.561874ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (194.938472ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (581.284797ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (250.971159ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (117.947836ms)
✔ H13: a retry with the same request ID and different text is refused (178.224438ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (565.102466ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (266.873236ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (197.785824ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (788.793455ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (364.242198ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (765.518356ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (274.096848ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (467.238094ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.644438ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (696.98591ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (534.823714ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (663.255287ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2894.542634ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (579.641478ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (8.612615ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.778002ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.622306ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.516118ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.78982ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.17778ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (2.12895ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.657323ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.070213ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.254685ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.034599ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.107931ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.063808ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.291747ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.769306ms)
✔ F9: registrations never add or redirect a root (1.621776ms)
✔ F10: a header cwd naming another project is refused (4.099532ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.712883ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.180716ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.413887ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.44242ms)
✔ F13: compaction is a marker in place, then the retained content (0.7474ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (559.266436ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.92926ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.593501ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.507037ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.171811ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.060714ms)
✔ every page and cursor is a valid CHAT-01 record (775.962247ms)
✔ the engine pin holds for the installed package (1.60809ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (290.183478ms)
✔ 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) (562.892909ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (239.923521ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (463.94818ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (493.384913ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (234.432969ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.044727ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (332.232127ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (217.059881ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (291.095661ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (452.398096ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1872.133223ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (185.106039ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (267.104913ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (135.381071ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (201.557644ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (387.186013ms)
✔ N10: fake conformance (32.738253ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (411.430629ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (262.717708ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (307.175677ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (400.04523ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (349.142564ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (522.621265ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (244.025144ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (416.122347ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (617.068048ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (546.464894ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (439.663588ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (202.543546ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (194.958221ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (203.023052ms)
✖ 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 (23.898095ms)
✖ 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 (1.214769ms)
ℹ tests 161
ℹ suites 0
ℹ pass 159
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 44308.337232
✖ failing tests:
test at packages/conversation/tests/turns.test.mjs:921:1
✖ 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 (23.898095ms)
AssertionError [ERR_ASSERTION]: Missing expected exception.
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/turns.test.mjs:945:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
test at packages/conversation/tests/turns.test.mjs:985:1
✖ 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 (1.214769ms)
AssertionError [ERR_ASSERTION]: Missing expected exception: {"command":"/bin/sh"}
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/turns.test.mjs:991:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: undefined,
operator: 'throws',
diff: 'simple'
}
@@ -0,0 +1,208 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (210.124284ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (31.461327ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.243448ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (402.28461ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (375.898502ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (245.697874ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (205.248272ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (200.967855ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.300381ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10019.459164ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28021.743344ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (293.52326ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (170.965273ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (467.834848ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (485.764314ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (357.426464ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (112.120278ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (179.999177ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (280.218842ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (794.918787ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (236.226156ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (99.019273ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (72.239023ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.233754ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.07543ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.784372ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2543.005043ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (427.579138ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2546.039593ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4729.425819ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2517.647784ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2456.065654ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2397.946545ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5013.495174ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (665.282732ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (565.141189ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (449.546236ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (580.520202ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (353.242935ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (440.882452ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3289.779978ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (38.995157ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (316.778234ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (297.186173ms)
✖ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (1.291193ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (203.795654ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (199.791926ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (221.921228ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (285.806839ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (183.605566ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.433561ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (200.894322ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (301.700912ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (204.230931ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (175.521114ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (208.038225ms)
✔ 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 (200.614244ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.007138ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (205.327098ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (221.041919ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (240.904641ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (193.703865ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.429915ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.332324ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.304665ms)
✔ 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.487566ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.002825ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.815852ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.380236ms)
✔ 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.370888ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.105714ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (219.132478ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (244.714778ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (287.860814ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (810.97954ms)
✔ H4: self-takeover is refused (196.716399ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (428.252517ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1026.91514ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (213.614887ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (707.448266ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (323.714535ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (280.799125ms)
✔ H13: a retry with the same request ID and different text is refused (227.071722ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (581.862104ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (284.28098ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (200.131163ms)
✔ 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.966075ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (352.507519ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (926.853888ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (252.575877ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (378.482834ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.743173ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (694.916678ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (634.538003ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (797.111125ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2923.654555ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (599.855939ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.541456ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.547317ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.519194ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (4.029671ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.477817ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.163806ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.954376ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.885908ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.039207ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.311931ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.962792ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.922015ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.083999ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.697783ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.940209ms)
✔ F9: registrations never add or redirect a root (1.617335ms)
✔ F10: a header cwd naming another project is refused (3.061892ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.738695ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.22052ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.070584ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.104794ms)
✔ F13: compaction is a marker in place, then the retained content (0.623633ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (537.366682ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.390694ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.475862ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.128417ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (0.993087ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.835524ms)
✔ every page and cursor is a valid CHAT-01 record (781.261909ms)
✔ the engine pin holds for the installed package (1.814596ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (251.074264ms)
✔ 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) (539.885355ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (250.831787ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (467.221861ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (456.140571ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (216.781065ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.790662ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (359.505509ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (198.253475ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (290.020188ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (461.038595ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1999.044479ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (243.055993ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (307.828333ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (206.716102ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (179.929887ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (391.461287ms)
✔ N10: fake conformance (32.726895ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (437.718821ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (283.355298ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (287.744185ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (357.831472ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (429.130986ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (504.83989ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (248.28802ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (324.654472ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (1053.254692ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (651.115448ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (530.842773ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (229.950926ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (212.523964ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (219.580102ms)
✔ 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 (169.353006ms)
✖ 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 (161.499097ms)
ℹ tests 161
ℹ suites 0
ℹ pass 159
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 45197.746311
✖ failing tests:
test at packages/conversation/tests/cohort.test.mjs:719:1
✖ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (1.291193ms)
AssertionError [ERR_ASSERTION]: XDG_RUNTIME_DIR
true !== false
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/cohort.test.mjs:727:53)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: true,
expected: false,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/turns.test.mjs:985:1
✖ 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 (161.499097ms)
AssertionError [ERR_ASSERTION]: NODE_TEST_CONTEXT
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/turns.test.mjs:1020:48)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,191 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (205.243368ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (32.971124ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.121787ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (363.092035ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (399.908161ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (258.536463ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (191.324727ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (198.171459ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.298721ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9540.859249ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28438.460806ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (236.541889ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (140.833118ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (352.485068ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (274.634527ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (332.945439ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (99.79432ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (168.079107ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (257.196308ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (707.796162ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (177.200989ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (87.206565ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (69.800339ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.739859ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.120244ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.763373ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2570.068602ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (427.02865ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2497.435582ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4713.208018ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2477.606579ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2437.935691ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2369.057138ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4841.240628ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (908.480524ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (686.740273ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (442.584651ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (983.93316ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (539.705712ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (518.578873ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3273.76047ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (38.212767ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (355.279405ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (296.183553ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.466304ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (209.004856ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (214.137217ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (201.61086ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (272.875125ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (190.064034ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.796296ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (199.574027ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (295.268898ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (209.386053ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (189.16872ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (219.84494ms)
✔ 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 (192.081571ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.677012ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (207.298698ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (222.116357ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (242.216792ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (201.786645ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.416713ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.293798ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.319004ms)
✔ 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.490272ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.087181ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.776708ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.392342ms)
✔ 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.402684ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.124997ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (220.254316ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (251.23024ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (307.429041ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (791.07685ms)
✔ H4: self-takeover is refused (191.115619ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (438.949148ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1037.817938ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (230.089636ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (639.672206ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (288.784191ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (177.231887ms)
✔ H13: a retry with the same request ID and different text is refused (182.57847ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (558.827796ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (273.421679ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (186.077439ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (822.301611ms)
✖ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (157.367344ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (709.37497ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (246.481901ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (411.858876ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.557871ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (738.819045ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (666.805349ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (714.737813ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2904.453949ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (585.679185ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.539397ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.689488ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.045989ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (4.213632ms)
✔ F1: an unreadable fork is never merged into another branch's history (2.241709ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.724827ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.968384ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.018734ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.153028ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.840292ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.351711ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.161304ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.900902ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.955017ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.310891ms)
✔ F9: registrations never add or redirect a root (1.669766ms)
✔ F10: a header cwd naming another project is refused (3.578602ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.842248ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.63283ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.496616ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.295625ms)
✔ F13: compaction is a marker in place, then the retained content (0.728643ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (530.342879ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.537373ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.413759ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.188444ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.117889ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.870566ms)
✔ every page and cursor is a valid CHAT-01 record (882.593802ms)
✔ the engine pin holds for the installed package (1.940486ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (287.41949ms)
✔ 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) (524.437299ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (276.875497ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (506.550697ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (427.410335ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (228.50453ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.288455ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (349.223622ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (201.95495ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (300.316082ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (467.772868ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1878.679275ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (175.277793ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (268.367786ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (181.146729ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (165.325529ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (335.041677ms)
✔ N10: fake conformance (32.262821ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (407.322016ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (259.001755ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (282.076696ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (347.971727ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (340.4005ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (413.9929ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (266.667027ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (465.879241ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (905.466694ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (574.171151ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (477.789525ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (232.373679ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (201.756147ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (179.078603ms)
✔ 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 (210.764407ms)
✔ 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 (135.591802ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 44476.458522
✖ failing tests:
test at packages/conversation/tests/races.test.mjs:566:1
✖ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (157.367344ms)
AssertionError [ERR_ASSERTION]: the flag is cleared on the throw
+ actual - expected
+ 'stop-3139b65e069e370d'
- null
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/races.test.mjs:576:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'stop-3139b65e069e370d',
expected: null,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,185 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (203.41786ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (30.836515ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.975065ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (353.712488ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (393.124663ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (251.188238ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (195.475876ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (183.457034ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.376639ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9602.448409ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (27952.100317ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (262.313942ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (144.571215ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (457.743846ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (344.484377ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (379.903393ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (126.943466ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (180.234541ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (452.027821ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (770.05324ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (205.609325ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (92.540488ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (74.345949ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.766246ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.129371ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.154138ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2539.100458ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (426.885276ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2527.265453ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4720.852699ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2489.036321ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2637.638113ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2399.580384ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4926.638042ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (678.124747ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (553.076107ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (426.134464ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (607.694768ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (389.844702ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (467.72868ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3292.379509ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (41.147415ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (321.698953ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (292.313267ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.679306ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (202.198347ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (190.064711ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (199.281057ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (274.346645ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (196.747279ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.484386ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (191.456364ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (297.864038ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (201.443252ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (199.763498ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (202.958219ms)
✔ 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 (208.068961ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.513842ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (192.192445ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (217.978453ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (243.384518ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (199.20547ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.399352ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.266554ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.314843ms)
✔ 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.481182ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.097987ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.804565ms)
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.384249ms)
✔ 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.40751ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.109987ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (227.591853ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (247.828826ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (297.856813ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (783.576315ms)
✔ H4: self-takeover is refused (193.79138ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (446.84611ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1023.235481ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (233.460026ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (685.766623ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (314.349745ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (226.566774ms)
✔ H13: a retry with the same request ID and different text is refused (214.067602ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (605.624418ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (276.653331ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (196.280549ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (865.003311ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (329.918418ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (761.550873ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (209.420542ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (379.313842ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.605402ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (720.00289ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (631.521911ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (731.925245ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2915.844105ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (576.100997ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.523027ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.615618ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.584543ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.144481ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.285966ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.639214ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.665467ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.055042ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.842976ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (6.510932ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (6.677964ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.737218ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.559182ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.704012ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.69923ms)
✔ F9: registrations never add or redirect a root (1.633646ms)
✔ F10: a header cwd naming another project is refused (4.037666ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.730933ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.164068ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.049537ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.299441ms)
✔ F13: compaction is a marker in place, then the retained content (0.756733ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (513.925774ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.425573ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.385008ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.103343ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.001552ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.770477ms)
✔ every page and cursor is a valid CHAT-01 record (799.018968ms)
✔ the engine pin holds for the installed package (1.61508ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (259.435881ms)
✔ 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) (525.770955ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (260.997836ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (460.188648ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (428.517568ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (225.28543ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.08887ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (346.262404ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (189.447164ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (305.267892ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (458.470205ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1865.195374ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (225.777454ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (301.828835ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (206.4257ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (225.01419ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (357.412059ms)
✔ N10: fake conformance (32.803959ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (434.616033ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (282.522584ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (284.534935ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (381.37991ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (351.13522ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (351.419345ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (220.666817ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (443.614269ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (851.415212ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (589.727861ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (496.127291ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (260.787049ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (208.774599ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (191.207041ms)
✔ 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 (225.124882ms)
✔ 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 (181.956861ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 44600.400016
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:729:1
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.384249ms)
Error: not connected
at stub.takeover (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:735:28)
at #act (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:226:80)
at file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:213:37
at #run (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:148:17)
at file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:131:49
at async waitForActual (node:assert:615:5)
at async strict.rejects (node:assert:738:25)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:739:3)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,249 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (204.054961ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (34.379721ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.823333ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (368.03835ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (381.961473ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (248.075537ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (194.16258ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (199.892251ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.409743ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9646.508618ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28683.355079ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (277.997362ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (150.982432ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (385.987741ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (309.420051ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (536.513285ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (222.313753ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (281.830815ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (539.996757ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1373.317081ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (198.122522ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (90.321015ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (70.600173ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.23192ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.107839ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.776203ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2525.510435ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (416.595939ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2525.015114ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4731.593488ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2492.593649ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2427.004346ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2344.666336ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4947.565009ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (718.282118ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (701.816499ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (494.381035ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (774.978815ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (378.622296ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (912.631395ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3356.226708ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (22.665709ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (330.626176ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (315.192513ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (92.815845ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (204.654339ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (229.325574ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (189.427229ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (260.756963ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (184.740503ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.449448ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (181.966044ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (295.124047ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (206.003713ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (185.664141ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (200.892038ms)
✔ 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 (213.646988ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.786118ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (207.464846ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (228.324171ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (233.822231ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (198.428572ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.458166ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.303444ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.387812ms)
✖ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (1.176916ms)
✖ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (10.235668ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.082787ms)
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.518385ms)
✖ 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.350038ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.110775ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (271.453398ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (249.859212ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (312.273393ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (769.744797ms)
✔ H4: self-takeover is refused (189.998676ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (423.547507ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1030.02182ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (226.142835ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (610.381036ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (272.715979ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (137.283715ms)
✔ H13: a retry with the same request ID and different text is refused (189.150552ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (551.849378ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (362.37526ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (209.161203ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (837.300297ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (336.911795ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (720.420356ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (191.926492ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (327.649024ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.625546ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (665.032178ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (575.669043ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (756.784345ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3010.580645ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (592.373753ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (9.103468ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.657269ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.743898ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.820379ms)
✔ F1: an unreadable fork is never merged into another branch's history (2.239145ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (3.168417ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (2.845088ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.491415ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.654332ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.067451ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.484527ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.33199ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.676737ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.858039ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.957907ms)
✔ F9: registrations never add or redirect a root (1.487184ms)
✔ F10: a header cwd naming another project is refused (3.962499ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.778615ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.766097ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.522762ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.169327ms)
✔ F13: compaction is a marker in place, then the retained content (0.69808ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (499.952617ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.66236ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.422413ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.261312ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.01753ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.860287ms)
✔ every page and cursor is a valid CHAT-01 record (842.463988ms)
✔ the engine pin holds for the installed package (2.852236ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (259.28921ms)
✔ 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) (512.779416ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (292.244852ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (505.271246ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (411.28012ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (225.967067ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.503213ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (342.057922ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (196.49213ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (290.083993ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (477.6962ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1887.787322ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (185.311504ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (266.673099ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (226.462671ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (288.90147ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (400.513262ms)
✔ N10: fake conformance (31.406735ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (426.755647ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (266.170917ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (277.390485ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (348.249457ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (355.074434ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (344.930861ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (140.398913ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (312.785854ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (770.727899ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (582.312023ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (548.270857ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (282.639537ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (199.232135ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (211.15431ms)
✔ 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 (208.274873ms)
✔ 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 (195.561529ms)
ℹ tests 161
ℹ suites 0
ℹ pass 157
ℹ fail 4
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 46343.156165
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:622:1
✖ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (1.176916ms)
AssertionError [ERR_ASSERTION]: text typed after the takeover is sent
+ actual - expected
+ []
- [
- 'hi'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:629:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: [],
expected: [ 'hi' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:680:1
✖ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (10.235668ms)
AssertionError [ERR_ASSERTION]: ["\u0007","\u0014","hi\r"]: the text is still held
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:704: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: null,
expected: null,
operator: 'notStrictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:729:1
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.518385ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
+ []
- [
- 'hi'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:744:10)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [],
expected: [ 'hi' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:754:1
✖ 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.350038ms)
AssertionError [ERR_ASSERTION]: ["\u0007\u0014hi\r"]
+ actual - expected
+ []
- [
- 'hi'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:767: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,182 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (444.489241ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.114009ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (20.803292ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (437.3869ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (401.575514ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (241.557301ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (218.171279ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (200.467573ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.353538ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9625.491807ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (30328.453661ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (572.328261ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (226.015185ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (557.996747ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (430.320709ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (482.984396ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (191.905232ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (270.708722ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (721.95802ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1366.114447ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (339.840753ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (115.697426ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (87.362233ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.893589ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.089914ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.002417ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3284.999053ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (448.989865ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2785.741884ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4828.594925ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2420.879439ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2502.580258ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2375.057737ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5161.12547ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (725.127878ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (565.417166ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (498.327322ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (1251.927386ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (396.063665ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (480.77333ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3389.883044ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (60.657475ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (833.090385ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (534.478549ms)
✖ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (2.927391ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (448.541556ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (207.475064ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (253.805985ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (305.816352ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (189.35486ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.430791ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (192.991854ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (318.916082ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (229.654079ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (214.346593ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (211.145867ms)
✔ 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 (214.990503ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.9355ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (219.181602ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (231.586346ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (267.004811ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (211.054009ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.489353ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.298263ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.341813ms)
✔ 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.519375ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.362917ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.875831ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.476889ms)
✔ 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.447666ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.108257ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (233.110746ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (490.744892ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (336.996034ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (839.949699ms)
✔ H4: self-takeover is refused (214.245529ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (476.054713ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1095.220116ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (244.046467ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (720.633738ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (284.51342ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (199.635372ms)
✔ H13: a retry with the same request ID and different text is refused (201.556848ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (591.750049ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (283.396543ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (205.07029ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (943.169717ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (353.797434ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (753.240116ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (192.330506ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (330.733447ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.728618ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (626.777369ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (510.956703ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (648.387826ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2925.028324ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (578.300644ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.498062ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.491119ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.449653ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.13226ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.346238ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.831007ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.690639ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.045602ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.382775ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.43909ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.478704ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.358496ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.594726ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.624349ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.999087ms)
✔ F9: registrations never add or redirect a root (1.482234ms)
✔ F10: a header cwd naming another project is refused (3.240941ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.827434ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.240274ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.797786ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.325569ms)
✔ F13: compaction is a marker in place, then the retained content (0.833734ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (546.515998ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.531678ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.42695ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.178693ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.082437ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.026942ms)
✔ every page and cursor is a valid CHAT-01 record (770.378589ms)
✔ the engine pin holds for the installed package (1.717473ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (285.09891ms)
✔ 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) (617.244257ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (236.213558ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (775.316092ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (476.949478ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (235.879383ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.939981ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (373.789641ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (215.369081ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (314.808142ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (508.15758ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1840.598738ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (200.532378ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (293.354862ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (208.14776ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (213.690716ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (372.326326ms)
✔ N10: fake conformance (33.462595ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (445.339651ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (314.724531ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (306.15793ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (374.286004ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (364.268546ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (348.461093ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (183.341635ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (350.625202ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (598.023105ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (494.015569ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (455.604102ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (200.048152ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (194.431362ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (183.07013ms)
✔ 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 (219.335665ms)
✔ 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 (217.267209ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 49224.239848
✖ failing tests:
test at packages/conversation/tests/cohort.test.mjs:719:1
✖ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (2.927391ms)
Error: the scope exited before the shim answered ({"code":1,"signal":null})
at ScopeLauncher.launch (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/cohort.mjs:113:45)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/cohort.test.mjs:729:16)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
code: 'launch-failed'
}
@@ -0,0 +1,51 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3951.299513ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4145.685127ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5815.89726ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (248.246257ms)
✖ bus routes are GET-only, have no write verb, and keep the same-origin checks (14.331615ms)
✔ bus refusals map to statuses and never carry a path or a stack (58.423309ms)
✔ humanCall speaks the human transport protocol without blocking the server (2583.156034ms)
✔ busReader takes --business, else the live bus host, else refuses (359.158216ms)
✔ humanCall kills a transport that runs past its timeout (529.2833ms)
✔ serve refuses a --business value that is not a business id (174.187442ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3887.05143ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2076.508456ms)
✔ conversation view: a newer session with no readable history keeps the marker (1083.482735ms)
✔ conversation view: seats without history say so and offer no reply (797.718462ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3790.919426ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54379.458865ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3797.085056ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21890.363687ms)
✔ loopback host and board origin fail closed (9.021567ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (105.753227ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (73.398536ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1461.685607ms)
ℹ tests 22
ℹ suites 0
ℹ pass 21
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54809.510942
✖ failing tests:
test at packages/webui/tests/bus.test.mjs:90:1
✖ bus routes are GET-only, have no write verb, and keep the same-origin checks (14.331615ms)
AssertionError [ERR_ASSERTION]: POST
200 !== 405
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus.test.mjs:96:77)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 200,
expected: 405,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,56 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3003.3722ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3114.111063ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4351.889677ms)
✖ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (276.696684ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (388.203302ms)
✔ bus refusals map to statuses and never carry a path or a stack (118.075829ms)
✔ humanCall speaks the human transport protocol without blocking the server (2288.9815ms)
✔ busReader takes --business, else the live bus host, else refuses (113.110792ms)
✔ humanCall kills a transport that runs past its timeout (538.538233ms)
✔ serve refuses a --business value that is not a business id (158.489801ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2969.53414ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1655.234956ms)
✔ conversation view: a newer session with no readable history keeps the marker (1160.899414ms)
✔ conversation view: seats without history say so and offer no reply (841.814023ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3093.32565ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53481.922902ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3084.069667ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21702.694009ms)
✔ loopback host and board origin fail closed (7.724318ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (150.087521ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (349.28937ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1217.653677ms)
ℹ tests 22
ℹ suites 0
ℹ pass 21
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54048.101847
✖ failing tests:
test at packages/webui/tests/bus.test.mjs:49:1
✖ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (276.696684ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
'',
+ 'invalid-response'
- 'invalid-request'
]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus.test.mjs:85:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ '', 'invalid-response' ],
expected: [ '', 'invalid-request' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,31 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2455.957142ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2677.423888ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4179.839474ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (242.675976ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (221.800586ms)
✔ bus refusals map to statuses and never carry a path or a stack (108.096567ms)
✔ humanCall speaks the human transport protocol without blocking the server (2372.69171ms)
✔ busReader takes --business, else the live bus host, else refuses (85.668748ms)
✔ humanCall kills a transport that runs past its timeout (527.652335ms)
✔ serve refuses a --business value that is not a business id (166.099183ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2520.247071ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1708.93403ms)
✔ conversation view: a newer session with no readable history keeps the marker (1129.849334ms)
✔ conversation view: seats without history say so and offer no reply (841.107983ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2617.740385ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53316.629363ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2648.859909ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21717.140369ms)
✔ loopback host and board origin fail closed (8.35513ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (99.237921ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (78.046439ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1223.612451ms)
ℹ tests 22
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53676.103745
@@ -0,0 +1,52 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2596.627211ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2656.24458ms)
✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2893.434971ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (285.55456ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (99.023379ms)
✔ bus refusals map to statuses and never carry a path or a stack (239.181445ms)
✔ humanCall speaks the human transport protocol without blocking the server (2424.804603ms)
✔ busReader takes --business, else the live bus host, else refuses (80.061707ms)
✔ humanCall kills a transport that runs past its timeout (527.407691ms)
✔ serve refuses a --business value that is not a business id (154.360857ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2573.003601ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1559.783589ms)
✔ conversation view: a newer session with no readable history keeps the marker (1021.105609ms)
✔ conversation view: seats without history say so and offer no reply (724.020956ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2444.135774ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53415.269145ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2473.191827ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21916.831767ms)
✔ loopback host and board origin fail closed (6.582995ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (93.174082ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (66.499708ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1234.234867ms)
ℹ tests 22
ℹ suites 0
ℹ pass 21
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53852.203465
✖ failing tests:
test at packages/webui/tests/bus-browser.test.mjs:68:1
✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (2893.434971ms)
AssertionError [ERR_ASSERTION]: decision
2 !== 0
at inertCheck (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus-browser.test.mjs:79:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus-browser.test.mjs:133:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 0,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,47 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3692.862605ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4399.78142ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5138.900983ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (240.785812ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (229.332019ms)
✔ bus refusals map to statuses and never carry a path or a stack (137.490595ms)
✔ humanCall speaks the human transport protocol without blocking the server (2050.349367ms)
✔ busReader takes --business, else the live bus host, else refuses (255.148435ms)
✖ humanCall kills a transport that runs past its timeout (1534.982657ms)
✔ serve refuses a --business value that is not a business id (173.289267ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3713.717292ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2111.922539ms)
✔ conversation view: a newer session with no readable history keeps the marker (1135.55318ms)
✔ conversation view: seats without history say so and offer no reply (796.483555ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4531.298745ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54222.404514ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4539.35007ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21792.672553ms)
✔ loopback host and board origin fail closed (6.686818ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (99.11453ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (99.935713ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1076.900081ms)
ℹ tests 22
ℹ suites 0
ℹ pass 21
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 61469.598771
✖ failing tests:
test at packages/webui/tests/bus.test.mjs:196:1
✖ humanCall kills a transport that runs past its timeout (1534.982657ms)
AssertionError [ERR_ASSERTION]: transport 2130788 still runs after the timeout
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus.test.mjs:207: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: false,
expected: true,
operator: '==',
diff: 'simple'
}
@@ -0,0 +1,54 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4083.605735ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4274.417716ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5604.426858ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (298.719751ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (455.827898ms)
✔ bus refusals map to statuses and never carry a path or a stack (98.277335ms)
✖ humanCall speaks the human transport protocol without blocking the server (824.313532ms)
✔ busReader takes --business, else the live bus host, else refuses (260.502986ms)
✔ humanCall kills a transport that runs past its timeout (537.16016ms)
✔ serve refuses a --business value that is not a business id (160.84085ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4074.532968ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2021.581003ms)
✔ conversation view: a newer session with no readable history keeps the marker (1166.832554ms)
✔ conversation view: seats without history say so and offer no reply (874.161758ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3919.185383ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54454.659071ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3922.938725ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21951.170269ms)
✔ loopback host and board origin fail closed (6.998828ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (94.95425ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (149.515077ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1384.946165ms)
ℹ tests 22
ℹ suites 0
ℹ pass 21
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54798.301609
✖ failing tests:
test at packages/webui/tests/bus.test.mjs:149:1
✖ humanCall speaks the human transport protocol without blocking the server (824.313532ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'resolved'
- 'response-too-large'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus.test.mjs:157:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'resolved',
expected: 'response-too-large',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,66 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2541.028381ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2817.107754ms)
✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (9034.138353ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (226.496809ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (75.759813ms)
✖ bus refusals map to statuses and never carry a path or a stack (9.012812ms)
✔ humanCall speaks the human transport protocol without blocking the server (2714.39824ms)
✔ busReader takes --business, else the live bus host, else refuses (115.769104ms)
✔ humanCall kills a transport that runs past its timeout (530.388769ms)
✔ serve refuses a --business value that is not a business id (179.231846ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2458.233556ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1643.141896ms)
✔ conversation view: a newer session with no readable history keeps the marker (1910.878603ms)
✔ conversation view: seats without history say so and offer no reply (814.993352ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2407.206463ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (52937.436576ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2420.954806ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21957.117114ms)
✔ loopback host and board origin fail closed (6.856516ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (85.519283ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (74.83407ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1375.540306ms)
ℹ tests 22
ℹ suites 0
ℹ pass 20
ℹ fail 2
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53283.086833
✖ failing tests:
test at packages/webui/tests/bus-browser.test.mjs:68:1
✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (9034.138353ms)
Error: Uncaught (in promise) Error: Condition timed out: document.querySelector("#bus-view h1").textContent==="Bus refused the read": undefined
at Object.evaluate (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/browser.mjs:44:35)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus-browser.test.mjs:235:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
test at packages/webui/tests/bus.test.mjs:106:1
✖ bus refusals map to statuses and never carry a path or a stack (9.012812ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
[
'human-required',
+ 502,
- 403,
'human-required'
]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus.test.mjs:117:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [ 'human-required', 502, 'human-required' ],
expected: [ 'human-required', 403, 'human-required' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,54 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2694.332875ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2786.251338ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4198.779179ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (248.724539ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (109.760828ms)
✔ bus refusals map to statuses and never carry a path or a stack (333.4259ms)
✔ humanCall speaks the human transport protocol without blocking the server (2558.626957ms)
✔ busReader takes --business, else the live bus host, else refuses (80.256537ms)
✔ humanCall kills a transport that runs past its timeout (526.387072ms)
✖ serve refuses a --business value that is not a business id (10054.738579ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2643.637933ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1519.888206ms)
✔ conversation view: a newer session with no readable history keeps the marker (1034.895305ms)
✔ conversation view: seats without history say so and offer no reply (782.595368ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2390.525218ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53307.043172ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2409.634346ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21953.548833ms)
✔ loopback host and board origin fail closed (8.953531ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (105.696867ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (111.406083ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1529.056823ms)
ℹ tests 22
ℹ suites 0
ℹ pass 21
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53688.834121
✖ failing tests:
test at packages/webui/tests/bus.test.mjs:210:1
✖ serve refuses a --business value that is not a business id (10054.738579ms)
AssertionError [ERR_ASSERTION]: ../acme
0 !== 2
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/webui/tests/bus.test.mjs:214:12)
at Test.runInAsyncScope (node:async_hooks:226:14)
at Test.run (node:internal/test_runner/test:1402:25)
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
at Test.postRun (node:internal/test_runner/test:1542:19)
at Test.run (node:internal/test_runner/test:1467:12)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 0,
expected: 2,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,185 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (217.396936ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (36.544565ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (21.507903ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (509.001469ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (404.163675ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (242.40283ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (208.807864ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (207.462225ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.318314ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10232.482422ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (31466.722864ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (335.284664ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (188.849899ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (450.670767ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (301.822469ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (340.189331ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (105.122836ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (171.631828ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (267.469163ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1004.22294ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (312.798794ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (148.578052ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (74.804698ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.46135ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.298974ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.793936ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2672.692957ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (479.782186ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2575.098594ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4782.558278ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2508.741174ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2635.517593ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2543.327683ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5117.317392ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (766.257735ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (703.483815ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (493.852682ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (838.499197ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (441.909021ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (627.890156ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3359.232229ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (40.675614ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (376.772979ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (413.568482ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.72845ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (221.922644ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (215.880869ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (329.259642ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (294.562439ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (215.950216ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.574922ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (211.859135ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (308.417243ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (217.735365ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (197.885125ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (227.209087ms)
✔ 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.067899ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.068684ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (239.108372ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (242.591519ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (280.448759ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (239.877174ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.424539ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.282157ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.314412ms)
✔ 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.507614ms)
✖ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (11.025845ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (6.302905ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.415843ms)
✔ 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.389792ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.123437ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (222.524377ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (270.754078ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (424.971884ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (848.438172ms)
✔ H4: self-takeover is refused (199.017247ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (459.423631ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1159.721239ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (284.251468ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (976.17191ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (385.642926ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (201.31102ms)
✔ H13: a retry with the same request ID and different text is refused (204.974928ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (644.134693ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (309.222357ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (228.643207ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (982.627072ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (522.547076ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (851.975086ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (227.017042ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (396.824157ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.630306ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (679.062429ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (568.330752ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (760.632219ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3076.593599ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (782.600239ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (8.750856ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.808138ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.23454ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.774025ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.88316ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.184033ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.801433ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.342579ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.922936ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.21217ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (6.510266ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.903471ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.670331ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.537823ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.006251ms)
✔ F9: registrations never add or redirect a root (1.495698ms)
✔ F10: a header cwd naming another project is refused (3.236016ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.953874ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.087118ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.452353ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.357137ms)
✔ F13: compaction is a marker in place, then the retained content (0.710358ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (529.313976ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.313232ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.39972ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.202044ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (0.986074ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.765077ms)
✔ every page and cursor is a valid CHAT-01 record (748.061593ms)
✔ the engine pin holds for the installed package (2.026407ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (280.796856ms)
✔ 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) (593.646992ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (240.141321ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (642.989325ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (450.085349ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (253.658679ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.048937ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (367.649695ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (211.176137ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (297.370414ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (513.73479ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1956.058807ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (193.257577ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (305.80327ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (203.839986ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (244.440611ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (434.025175ms)
✔ N10: fake conformance (30.855908ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (478.715547ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (299.164409ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (316.810557ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (417.870515ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (523.550464ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (418.666842ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (210.675714ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (412.485444ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (846.702599ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (610.708731ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (561.1203ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (282.574801ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (333.782942ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (222.214704ms)
✔ 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 (260.396141ms)
✔ 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.986666ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 49112.138906
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:680:1
✖ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (11.025845ms)
AssertionError [ERR_ASSERTION]: ["\u0007","\u0014","hi\r"]: the text is still held
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:704: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: null,
expected: null,
operator: 'notStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,169 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (214.364681ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (34.729116ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.669713ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (444.981908ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (385.441519ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (235.156238ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (197.163272ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (202.451129ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.287062ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10475.640184ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (31591.493553ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (347.511748ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (216.811217ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (529.922331ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (363.123069ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (477.537801ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (190.129522ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (247.113343ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (558.514571ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1401.855724ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (331.477014ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (123.99506ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (93.586794ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.722953ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.08199ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.941273ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3045.060309ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (547.16907ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2994.327317ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4948.134867ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2493.863571ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2613.418738ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2475.844123ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5192.741498ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (995.950666ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (745.643408ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (687.714102ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (1015.407081ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (648.192036ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (501.900788ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3273.802319ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (40.464229ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (334.892716ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (305.895112ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (63.442695ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (208.533815ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (201.238056ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (273.500065ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (283.65704ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (198.182318ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.412127ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (187.380019ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (302.657846ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (208.393901ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (202.517874ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (200.696044ms)
✔ 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 (208.982831ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.655091ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (211.345338ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (235.583819ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (255.215608ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (281.307156ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.374461ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.282956ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.292843ms)
✔ 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.468686ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.776404ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.664671ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.314455ms)
✔ 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.36567ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.112226ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (233.564714ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (257.108257ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (281.278331ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (852.719203ms)
✔ H4: self-takeover is refused (187.732732ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (443.496829ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1060.991154ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (278.839204ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (1184.532916ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (353.164261ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (194.182751ms)
✔ H13: a retry with the same request ID and different text is refused (209.626616ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (631.035486ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (295.166919ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (205.602705ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (990.571478ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (421.756016ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (860.018621ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (184.053928ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (357.436635ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.630687ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (659.026885ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (578.43071ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (748.558247ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2968.871933ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (671.19246ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.514664ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.809684ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.699252ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.465703ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.510913ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.917931ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.764385ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.294389ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.311561ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.900826ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.406131ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.405912ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.411023ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.640201ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.732751ms)
✔ F9: registrations never add or redirect a root (1.645064ms)
✔ F10: a header cwd naming another project is refused (4.037322ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.708086ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.848189ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.967854ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.380699ms)
✔ F13: compaction is a marker in place, then the retained content (0.920617ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (532.68293ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.40052ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.364256ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.135617ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (0.983498ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.803055ms)
✔ every page and cursor is a valid CHAT-01 record (750.611034ms)
✔ the engine pin holds for the installed package (1.871052ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (267.127695ms)
✔ 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) (532.759336ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (244.669222ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (486.531938ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (514.964805ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (230.069796ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.122058ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (345.070237ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (197.504474ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (289.823497ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (464.644616ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1927.309525ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (206.288921ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (248.106136ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (196.491371ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (253.454964ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (404.111409ms)
✔ N10: fake conformance (32.092362ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (395.709292ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (272.084511ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (311.87729ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (484.304304ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (414.437634ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (433.903006ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (218.334531ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (396.556351ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (811.864982ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (537.831578ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (688.752083ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (265.268271ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (228.796739ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (225.258216ms)
✔ 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.264821ms)
✔ 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.697341ms)
ℹ tests 161
ℹ suites 0
ℹ pass 161
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 50589.291575
@@ -0,0 +1,230 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (242.343624ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (35.697982ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (16.132322ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (503.035588ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (443.167414ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (274.703828ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (232.07581ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (230.15877ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (3.009835ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11546.378521ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (34120.00025ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (272.61315ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (153.357591ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (404.116951ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (291.262964ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (498.344358ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (100.527268ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (183.134098ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (408.823442ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1316.543463ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (192.188454ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (87.170177ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (75.31061ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.915662ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.063805ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.771087ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2988.20149ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (506.128963ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2974.04487ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5239.345122ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2737.972477ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2562.092637ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2460.787267ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5171.766888ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (823.867761ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (3524.732578ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (1031.83274ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (1193.641921ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (435.095244ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (808.437384ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3477.854872ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (32.402224ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (455.322918ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (310.652312ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.022684ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (238.403999ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (219.621604ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (303.908244ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (325.687584ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (228.402395ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.462713ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (225.651874ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (335.589092ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (227.102037ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (217.428512ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (251.797632ms)
✔ 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 (239.843387ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.772825ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (254.024052ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (273.039856ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (270.215357ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (340.470777ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.441265ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.286213ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.359653ms)
✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (1.118194ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.611268ms)
✖ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.843415ms)
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (1.229847ms)
✖ 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.399282ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.130937ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (241.207251ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (278.928619ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (318.718404ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (962.430536ms)
✔ H4: self-takeover is refused (238.61571ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (480.599671ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1242.995511ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (366.445367ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (960.859047ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (378.710397ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (188.588542ms)
✔ H13: a retry with the same request ID and different text is refused (221.464586ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (691.032805ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (308.03834ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (365.854057ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1009.706804ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (612.589352ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (951.678639ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (215.145569ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (385.249977ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.642903ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (686.817238ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (559.169845ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (963.501615ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2998.854354ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (681.607038ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.721308ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.305677ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.191545ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.597571ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.680524ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.061867ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.774014ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.408537ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.478021ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.988918ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.098035ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.639327ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.512466ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.06773ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.172832ms)
✔ F9: registrations never add or redirect a root (1.667012ms)
✔ F10: a header cwd naming another project is refused (3.975288ms)
✔ F11: parentSession renders with a marker and the parent is never opened (1.362522ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (7.322526ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (3.676783ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.359374ms)
✔ F13: compaction is a marker in place, then the retained content (0.740509ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (578.559604ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.484789ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.758594ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.687208ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.265538ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.512073ms)
✔ every page and cursor is a valid CHAT-01 record (858.408038ms)
✔ the engine pin holds for the installed package (2.78358ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (302.995856ms)
✔ 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) (585.010443ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (284.883315ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (532.607254ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (580.898622ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (274.671144ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.18143ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (417.589193ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (222.373716ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (325.759633ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (554.614239ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1975.221782ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (227.991096ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (315.828165ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (235.040587ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (267.803978ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (469.961288ms)
✔ N10: fake conformance (31.485735ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (627.386401ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (334.222906ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (311.277382ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (553.481603ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (494.790784ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (479.029771ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (234.327841ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (396.661213ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (828.119974ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (599.354327ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (831.434088ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (288.73939ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (224.631089ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (216.287649ms)
✔ 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 (259.470155ms)
✔ 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 (215.481299ms)
ℹ tests 161
ℹ suites 0
ℹ pass 158
ℹ fail 3
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 53597.577915
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:714:1
✖ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.843415ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'x\r' !== null
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:724:10)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'x\r',
expected: null,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:729:1
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (1.229847ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
+ []
- [
- 'hi'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:744:10)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: [],
expected: [ 'hi' ],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:754:1
✖ 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.399282ms)
AssertionError [ERR_ASSERTION]: ["\u0007\u0014hi\r"]
+ actual - expected
+ []
- [
- 'hi'
- ]
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:767: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,185 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (198.372123ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (31.075337ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.373348ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (355.177554ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (386.29791ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (237.446176ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (183.890713ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (168.003649ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.343801ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9910.02064ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (30025.136994ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (261.020047ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (170.591496ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (427.490668ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (327.603916ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (386.852464ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (119.534133ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (221.43074ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (474.129243ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (873.351748ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (202.273476ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (87.958981ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (80.045461ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.455718ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.269502ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.851127ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2956.932282ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (409.554265ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2741.682168ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4953.800589ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2685.420378ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2520.011232ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2457.739866ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4862.365858ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (781.11378ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (644.518794ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (487.919147ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (705.807459ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (404.977896ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (838.80529ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (4277.41095ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (23.475978ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (462.780073ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (321.765474ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (61.301427ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (193.552991ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (192.007312ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (194.571228ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (288.709781ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (197.752705ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.412047ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (182.207429ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (290.391192ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (189.715577ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (181.057322ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (198.103971ms)
✔ 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 (189.590486ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.476501ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (193.394641ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (210.509504ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (231.22304ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (192.46219ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.376815ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.260177ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.297571ms)
✔ 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.458736ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.010788ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.777672ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.421599ms)
✖ 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.468862ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.099303ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (221.76137ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (239.465539ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (287.546555ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (801.077593ms)
✔ H4: self-takeover is refused (172.410151ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (412.215706ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (962.843511ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (210.382637ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (698.37281ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (293.460832ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (185.903361ms)
✔ H13: a retry with the same request ID and different text is refused (181.953284ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (562.366901ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (279.101854ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (201.806792ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1051.581951ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (444.522329ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (924.47988ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (312.45617ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (400.296077ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.620839ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (644.50411ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (543.247699ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (709.531028ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3110.410899ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (639.668441ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.053198ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.639721ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.602125ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.310523ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.455177ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.896293ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.865939ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.205055ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.274164ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.992576ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.242979ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.40804ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (11.796102ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (14.330433ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.579ms)
✔ F9: registrations never add or redirect a root (1.723871ms)
✔ F10: a header cwd naming another project is refused (3.400678ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.764354ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.550319ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.385434ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.198296ms)
✔ F13: compaction is a marker in place, then the retained content (0.708568ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (504.827036ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.710643ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.459074ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.108167ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.005758ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.837085ms)
✔ every page and cursor is a valid CHAT-01 record (739.821669ms)
✔ the engine pin holds for the installed package (1.794769ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (261.345544ms)
✔ 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) (508.285075ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (241.64749ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (455.572594ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (431.366536ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (227.257958ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.586792ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (332.506983ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (166.500065ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (275.207131ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (441.37802ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1866.639068ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (169.536716ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (266.810678ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (191.019753ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (189.534947ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (383.401822ms)
✔ N10: fake conformance (32.726139ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (401.412059ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (390.429835ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (389.893653ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (432.632111ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (447.361456ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (468.568149ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (313.406676ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (382.141148ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (735.547023ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (463.527134ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (520.723104ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (236.259731ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (163.030636ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (207.182651ms)
✔ 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 (331.784523ms)
✔ 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 (304.230941ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 47088.300087
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:754:1
✖ 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.468862ms)
Error: boom
at stub.interrupt (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:774:11)
at #act (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:227:82)
at file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:213:37
at #run (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:148:17)
at file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/src/terminal.mjs:131:65
at async waitForActual (node:assert:615:5)
at async strict.rejects (node:assert:738:25)
at async TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:780:3)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
@@ -0,0 +1,190 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (351.268491ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (52.925148ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (22.624274ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (414.408093ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (479.704085ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (308.15834ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (350.06094ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (340.27323ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.240534ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11758.542981ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (30969.290753ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (388.826647ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (225.94793ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (562.638604ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (381.952208ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (549.830236ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (125.720864ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (257.84909ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (426.37923ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (1597.797046ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (353.02513ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (95.291508ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (76.534257ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.777212ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.080473ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.937441ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3198.627171ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (645.327369ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (3084.924791ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5313.405745ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2527.159289ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2741.068025ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2596.518112ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5005.189729ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (849.483928ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (696.276698ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (592.815666ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (616.919564ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (308.43354ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (481.238265ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3253.744906ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (31.898634ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (353.948313ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (283.212297ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (86.03424ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (426.038841ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (237.511352ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (219.383449ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (345.90601ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (259.530822ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.516963ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (336.079309ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (455.272368ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (287.70866ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (248.887121ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (266.202438ms)
✔ 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.468016ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.782119ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (322.253869ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (396.758856ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (509.482486ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (271.821424ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.416243ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.300669ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.299783ms)
✔ 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.517571ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (30.161847ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.739434ms)
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (1.010299ms)
✔ 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.39109ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.114537ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (234.356612ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (329.076951ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (326.997833ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (1132.941562ms)
✔ H4: self-takeover is refused (356.786928ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (547.886141ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1715.517376ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (298.883867ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (1055.989732ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (393.793084ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (294.394929ms)
✔ H13: a retry with the same request ID and different text is refused (251.53385ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (904.501951ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (317.983793ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (254.29771ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1089.038778ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (426.213203ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (1033.799044ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (334.232344ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (518.112129ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.776375ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (727.456025ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (609.916551ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (724.77625ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3092.95995ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (747.061881ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.918514ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.033536ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.683024ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.501767ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.795468ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.990277ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.840385ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.25527ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.714746ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (95.333905ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (6.374853ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.79805ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.25152ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.945091ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.917325ms)
✔ F9: registrations never add or redirect a root (1.449481ms)
✔ F10: a header cwd naming another project is refused (3.148556ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.739579ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.438389ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.162567ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.180821ms)
✔ F13: compaction is a marker in place, then the retained content (0.66641ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (574.034954ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.412441ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.405432ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.151952ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.022501ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.85587ms)
✔ every page and cursor is a valid CHAT-01 record (880.815632ms)
✔ the engine pin holds for the installed package (1.865873ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (293.114063ms)
✔ 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) (599.196039ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (297.762295ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (750.026135ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (512.681898ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (293.454307ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.29253ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (638.379943ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (291.85477ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (359.362405ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (616.665022ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (2223.855727ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (235.588795ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (324.917052ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (243.785601ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (325.648183ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (702.399356ms)
✔ N10: fake conformance (33.253893ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (483.580084ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (333.019787ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (348.577398ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (471.124136ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (455.169012ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (423.051907ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (274.915158ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (488.230298ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (1199.860528ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (582.978742ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (595.051706ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (228.873049ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (192.227752ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (190.696553ms)
✔ 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 (256.562604ms)
✔ 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 (226.824721ms)
ℹ tests 161
ℹ suites 0
ℹ pass 160
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 52035.348439
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:729:1
✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (1.010299ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ 'prompt: admitted'
- 'failed: not connected'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:751:10)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'prompt: admitted',
expected: 'failed: not connected',
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,302 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (349.326694ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (35.195899ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (22.555716ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (417.46445ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (759.910184ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (255.490138ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (215.13466ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (220.343429ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (3.861777ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10391.602554ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (29649.81262ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (261.6005ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (147.298178ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (386.547621ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (269.857317ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (336.910644ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (108.022741ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (196.514531ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (286.016907ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (839.509482ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (199.043932ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (94.744097ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (74.023836ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.36339ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.118715ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.293078ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3320.69157ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (461.566521ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2759.720592ms)
✔ K4: two engines; force stop one; the other survives by independent observation (5025.410566ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2513.765706ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2593.319868ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2656.485351ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4973.943053ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (759.14089ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (580.905235ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (424.841658ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (739.764604ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (354.115242ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (569.666002ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3417.469095ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (22.249903ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (384.239743ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (337.899051ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (62.152162ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (394.158946ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (229.401531ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (220.558237ms)
✖ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (588.583704ms)
✖ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (203.610708ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.509871ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (210.129857ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (318.592355ms)
✖ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (228.870252ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (219.053971ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (222.876779ms)
✔ 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 (227.31436ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.00791ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (220.066318ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (243.465125ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (275.856851ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (220.394413ms)
✖ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.467625ms)
✖ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.277144ms)
✖ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.53501ms)
✔ 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.477545ms)
✖ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (10.195424ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.963015ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.407563ms)
✔ 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.378982ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.102465ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (219.699407ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (279.346116ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (321.84638ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (1197.806029ms)
✔ H4: self-takeover is refused (213.250629ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (471.254671ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1152.00961ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (251.38588ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (705.40724ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (369.664002ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (219.822517ms)
✔ H13: a retry with the same request ID and different text is refused (214.564396ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (639.102842ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (310.819701ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (228.542202ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (901.038649ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (405.416345ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (868.704509ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (219.159547ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (348.601131ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.640556ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (660.301062ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (581.695977ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (773.576823ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3130.384165ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (673.561238ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.667932ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.783514ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.729456ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.551061ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.114104ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.969484ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.862926ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.36409ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.229195ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (7.362052ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.187847ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.253518ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.785625ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.043299ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.880281ms)
✔ F9: registrations never add or redirect a root (1.637364ms)
✔ F10: a header cwd naming another project is refused (3.577543ms)
✔ F11: parentSession renders with a marker and the parent is never opened (13.243468ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.538872ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.767818ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.462918ms)
✔ F13: compaction is a marker in place, then the retained content (0.878114ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (554.449948ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.218644ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.552193ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.454018ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.139676ms)
✔ a seat directory without search permission refuses that root, not the catalogue (2.091055ms)
✔ every page and cursor is a valid CHAT-01 record (796.018364ms)
✔ the engine pin holds for the installed package (1.943661ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (277.536368ms)
✔ 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) (602.476165ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (254.508983ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (715.093527ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (836.255359ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (249.134536ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.11236ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (383.522562ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (219.237408ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (314.142913ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (511.255907ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1934.70553ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (203.540315ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (312.744105ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (232.452897ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (236.963845ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (385.391097ms)
✔ N10: fake conformance (32.084229ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (485.374587ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (310.486078ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (286.618921ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (405.383504ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (410.27868ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (409.718676ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (232.572275ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (391.174062ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (771.295477ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (615.528123ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (605.47142ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (249.059677ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (184.407848ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (235.350706ms)
✔ 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 (250.279525ms)
✔ 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 (217.941414ms)
ℹ tests 161
ℹ suites 0
ℹ pass 154
ℹ fail 7
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 47469.552169
✖ failing tests:
test at packages/conversation/tests/flows.test.mjs:129:1
✖ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (588.583704ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'/null' !== '/'
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:135:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: '/null',
expected: '/',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:156:1
✖ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (203.610708ms)
AssertionError [ERR_ASSERTION]: a paste is literal and never submits by itself
+ actual - expected
+ 'run the deploy\n/goal xnull'
- 'run the deploy\n/goal x'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:169:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'run the deploy\n/goal xnull',
expected: 'run the deploy\n/goal x',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:243:1
✖ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (228.870252ms)
AssertionError [ERR_ASSERTION]: the draft is kept
+ actual - expected
+ 'half-typed draftnull'
- 'half-typed draft'
^
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:262: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: 'half-typed draftnull',
expected: 'half-typed draft',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:522:1
✖ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.467625ms)
AssertionError [ERR_ASSERTION]: an empty Enter sends nothing; nothing is resent
2 !== 1
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:542:10)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 2,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:559:1
✖ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.277144ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
'null' !== ''
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:565:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'null',
expected: '',
operator: 'strictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:576:1
✖ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.53501ms)
AssertionError [ERR_ASSERTION]: cut 1: nothing sent
+ actual - expected
+ [
+ 'ull[200~a'
+ ]
- []
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:583:12)
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.run (node:internal/test_runner/test:1409:7)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: [ 'ull[200~a' ],
expected: [],
operator: 'deepStrictEqual',
diff: 'simple'
}
test at packages/conversation/tests/flows.test.mjs:680:1
✖ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (10.195424ms)
AssertionError [ERR_ASSERTION]: ["\u0007","\u0014","hi\r"]: the text is still held
at TestContext.<anonymous> (file:///home/jwoltje/darkwing-scratch/r40c/wt/packages/conversation/tests/flows.test.mjs:704: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: null,
expected: null,
operator: 'notStrictEqual',
diff: 'simple'
}
@@ -0,0 +1,6 @@
#!/bin/bash
cd ~/darkwing-scratch/r40c/mut; : > summary.txt
for m in Ma-no-approve Mb-seal-command Mc-engine-keys Md-env-inherit Me-escalating Mf-queue-catch Mg-no-hold Mh-manager-env Mq-drain-any Mr-no-recompute Ms-no-try Mt-held-rethrow Mu-no-report Mv-feed-holds-true; do bash run.sh $m conversation; done
for m in Mi-bus-post Mj-subject Mk-copy-safe Ml-option-inert Mm-timeout-kill Mn-max-bytes Mo-status Mp-business-id; do bash run.sh $m webui; done
cd ../wt && sha256sum -c ../candidate-manifest-r3.sha256 2>&1 | grep -c ': OK$' > ../mut/manifest-after.txt
echo DONE >> ../mut/summary.txt
@@ -0,0 +1 @@
27
@@ -0,0 +1,33 @@
# mutate.py <id>: apply one named mutant (exact text, must match once). Prints the file.
import sys
C = "packages/conversation/src/"; W = "packages/webui/src/"
M = {
"Ma-no-approve": (C + "pi-pin.mjs", '"--no-themes", "--no-approve"]', '"--no-themes"]'),
"Mb-seal-command": (C + "controller.mjs", "if (this.engine.sealed && (", "if (false && ("),
"Mc-engine-keys": (C + "controller.mjs", "if (extra !== undefined) throw", "if (false) throw"),
"Md-env-inherit": (C + "pi-pin.mjs", ' for (const k of [...ENGINE_ENV, ...envKeys]) if (typeof source[k] === "string") env[k] = source[k];', " Object.assign(env, source);"),
"Me-escalating": (C + "controller.mjs", " if (this.escalating === s.id) this.escalating = null;\n", ""),
"Mf-queue-catch": (C + "terminal.mjs", "this.queue.catch(() => {}).then(", "this.queue.then("),
"Mg-no-hold": (C + "terminal.mjs", " this.held = s.slice(i + 1);\n break;\n", ""),
"Mh-manager-env": (C + "cohort.mjs", "env: { ...manager, ...env }", "env"),
"Mi-bus-post": (W + "serve.mjs", " if (req.method !== 'GET') return json(res, 405, { error: 'method not allowed' });\n return busRead", " return busRead"),
"Mj-subject": (W + "serve.mjs", "if (!subjectOk(subject))", "if (false)"),
"Mk-copy-safe": (W + "public/bus.js", "SAFE.test(String(d.id)) && SAFE.test(String(o.key))", "true"),
"Ml-option-inert": (W + "public/bus.js", "${txt(o.text)}", "${o.text}"),
"Mm-timeout-kill": (W + "bus.mjs", "const timer = setTimeout(() => { child.kill('SIGKILL'); done(", "const timer = setTimeout(() => { done("),
"Mn-max-bytes": (W + "bus.mjs", "if (size > maxBytes)", "if (false)"),
"Mo-status": (W + "bus.mjs", "'human-required': 403,", "'human-required': 502,"),
"Mp-business-id": (W + "cli.mjs", "if (!ID_PATTERN.test(value)) throw", "if (false) throw"),
"Mq-drain-any": (C + "terminal.mjs", " if (!holds) break;\n", " if (this.held === null) break;\n"),
"Mr-no-recompute": (C + "terminal.mjs", " actions = this.#parse(rest);\n holds = this.held !== null;\n", " actions = this.#parse(rest);\n holds = false;\n"),
"Ms-no-try": (C + "terminal.mjs", " try {\n await run();\n } catch (err) {\n if (report) report(err);\n else failure ??= err;\n }\n", " await run();\n"),
"Mt-held-rethrow": (C + "terminal.mjs", " this.held += data;\n return this.queue.catch(() => {});\n", " this.held += data;\n return this.queue;\n"),
"Mu-no-report": (C + "terminal.mjs", " if (report) report(err);\n else failure ??= err;\n", " failure ??= err;\n"),
"Mv-feed-holds-true": (C + "terminal.mjs", " const holds = this.held !== null;\n", " const holds = true;\n"),
}
f, old, new = 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,14 @@
#!/bin/bash
# run.sh <mutant> <pkg>: back up, mutate, run the package's node suite, restore from the backup.
set -u
cd ~/darkwing-scratch/r40c/wt
export TMPDIR=~/darkwing-scratch/tmp
m=$1; p=$2
f=$(python3 -c "import sys; sys.argv=['x','$m']; exec(open('../mut/mutate.py').read().split('f, old, new')[0]); print(M['$m'][0])")
cp -p "$f" ../mut/bak/current
python3 ../mut/mutate.py "$m" >/dev/null || { cp -p ../mut/bak/current "$f"; echo "$m: no match" | tee -a ../mut/summary.txt; exit 1; }
out=../mut/$m-$p.txt
timeout 600 node --test "packages/$p/tests/*.test.mjs" > "$out" 2>&1; rc=$?
cp -p ../mut/bak/current "$f"
pass=$(grep -E '^ℹ pass' "$out" | awk '{print $3}'); fail=$(grep -E '^ℹ fail' "$out" | awk '{print $3}')
echo "$m ($f): $p rc $rc pass $pass fail $fail" | tee -a ../mut/summary.txt
@@ -0,0 +1,23 @@
Ma-no-approve (packages/conversation/src/pi-pin.mjs): conversation rc 1 pass 160 fail 1
Mb-seal-command (packages/conversation/src/controller.mjs): conversation rc 1 pass 160 fail 1
Mc-engine-keys (packages/conversation/src/controller.mjs): conversation rc 1 pass 159 fail 2
Md-env-inherit (packages/conversation/src/pi-pin.mjs): conversation rc 1 pass 159 fail 2
Me-escalating (packages/conversation/src/controller.mjs): conversation rc 1 pass 160 fail 1
Mf-queue-catch (packages/conversation/src/terminal.mjs): conversation rc 1 pass 160 fail 1
Mg-no-hold (packages/conversation/src/terminal.mjs): conversation rc 1 pass 157 fail 4
Mh-manager-env (packages/conversation/src/cohort.mjs): conversation rc 1 pass 160 fail 1
Mq-drain-any (packages/conversation/src/terminal.mjs): conversation rc 1 pass 160 fail 1
Mr-no-recompute (packages/conversation/src/terminal.mjs): conversation rc 0 pass 161 fail 0
Ms-no-try (packages/conversation/src/terminal.mjs): conversation rc 1 pass 158 fail 3
Mt-held-rethrow (packages/conversation/src/terminal.mjs): conversation rc 1 pass 160 fail 1
Mu-no-report (packages/conversation/src/terminal.mjs): conversation rc 1 pass 160 fail 1
Mv-feed-holds-true (packages/conversation/src/terminal.mjs): conversation rc 1 pass 154 fail 7
Mi-bus-post (packages/webui/src/serve.mjs): webui rc 1 pass 21 fail 1
Mj-subject (packages/webui/src/serve.mjs): webui rc 1 pass 21 fail 1
Mk-copy-safe (packages/webui/src/public/bus.js): webui rc 0 pass 22 fail 0
Ml-option-inert (packages/webui/src/public/bus.js): webui rc 1 pass 21 fail 1
Mm-timeout-kill (packages/webui/src/bus.mjs): webui rc 1 pass 21 fail 1
Mn-max-bytes (packages/webui/src/bus.mjs): webui rc 1 pass 21 fail 1
Mo-status (packages/webui/src/bus.mjs): webui rc 1 pass 20 fail 2
Mp-business-id (packages/webui/src/cli.mjs): webui rc 1 pass 21 fail 1
DONE
@@ -0,0 +1,132 @@
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (11.497625ms)
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.292748ms)
✔ completed smoke replies and ordinary questions are idle, not human blockers (0.73417ms)
✔ only an explicit first-line input request makes a finished reply waiting (0.151844ms)
✔ tool activity, user text, errors and unfinished turns override attention text (0.126081ms)
✔ STOP access failure is unknown, not absence, under a non-root identity (35.372807ms)
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (2.626802ms)
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.269756ms)
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (5.602663ms)
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.543234ms)
✔ server rescans connector discovery and refuses HTTP reply without transport (34.032559ms)
✔ connector session links and linked directories are not read (1.163038ms)
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.952802ms)
✔ CLI print uses the relaunch notice instead of old current preview (58.598175ms)
✔ connector owner and fixed task never inherit a native relaunch notice (2.055349ms)
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.070293ms)
✔ loadConfig: missing file throws ConfigError (1.597423ms)
✔ loadConfig: invalid JSON throws ConfigError (0.335575ms)
✔ loadConfig: missing dataRoot throws ConfigError (0.242893ms)
✔ loadConfig: relative dataRoot throws ConfigError (0.228208ms)
✔ loadConfig: valid config returns dataRoot (0.67885ms)
✔ findNewestSession: picks the newest by mtime among two files (0.456236ms)
✔ findNewestSession: finds files in nested subdirectories (0.293162ms)
✔ findNewestSession: returns null for a missing dir (0.133449ms)
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.655969ms)
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.28215ms)
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.518602ms)
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.153363ms)
✔ deriveState: full state table (0.191897ms)
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.329398ms)
✔ rule: newest entry is a tool result with no assistant text after it is working (0.287165ms)
✔ rule: a finished ordinary turn is idle, even if it says your move (0.292841ms)
✔ task: the first user message of the session, from text blocks (0.251449ms)
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.262458ms)
✔ task: no user message in the log means null (shown as unknown), never a guess (0.284624ms)
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.413839ms)
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.540086ms)
✔ scan: the written record carries task, workspace and activeProject (0.567503ms)
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.593814ms)
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.389377ms)
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (0.998733ms)
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.432052ms)
✔ loadRegistrations: a missing seatsDir gives empty lists (0.152121ms)
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.725057ms)
✔ matchRegistration: matches by sessionsDir, and by realpath through a symlink; sessionsDir null never matches; same seat name with a different sessionsDir does not match (fleet vs repo darkwing) (0.325035ms)
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.688081ms)
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.259151ms)
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.554828ms)
✔ scanAgent: ageSeconds is computed from the injected now (0.26272ms)
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.170915ms)
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.347837ms)
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.36176ms)
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.869696ms)
✔ scan: relative boardDir throws ConfigError (0.092617ms)
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (59.687655ms)
✔ CLI: missing config exits 2 with a refused: message (56.624867ms)
✔ CLI: unknown command exits 2 (47.940261ms)
✔ CLI: unknown --liveness value exits 2 (51.16327ms)
✔ panesRunPi: true when any trimmed line equals 'pi' (0.176108ms)
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.077922ms)
✔ tmuxIsAlive: a pane running pi is alive (0.174784ms)
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.08773ms)
✔ tmuxIsAlive: no such tmux session is not alive (0.05117ms)
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.067521ms)
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.099285ms)
✔ parsePanes: one pane per line, command and optional tab-separated path (0.083031ms)
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.078034ms)
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.088586ms)
✔ loadSeen: missing file returns {} (0.145413ms)
✔ loadSeen: invalid JSON throws ConfigError (0.181617ms)
✔ loadSeen: a JSON array throws ConfigError (0.149855ms)
✔ loadSeen: a non-string value throws ConfigError (0.155826ms)
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.347995ms)
✔ markSeen: seen false deletes the key (0.259578ms)
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.201353ms)
✔ markSeen: project containing '/' throws ConfigError (0.118416ms)
✔ markSeen: non-boolean seen throws ConfigError (0.113757ms)
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.265895ms)
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.234309ms)
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.220257ms)
✔ scanAgent: an error-state session with a matching mark is seen (0.238872ms)
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.491888ms)
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.174158ms)
✔ taskSetBy: a registered task carries the record's setter; a record without the field (pre-#1511) reads unknown; the value is not copied into registered (0.650523ms)
✔ taskSetBy: null whenever the task shown is not the registered one: no registration, an empty registered task, a stale registration; the field is always present (0.575227ms)
✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (0.931945ms)
✔ isLoopbackHost: recognizes loopback hosts (1.236351ms)
✔ isLoopbackHost: rejects non-loopback hosts (4.605657ms)
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.139722ms)
✔ startServer: serves page, healthz, and a rescanning /api/board (33.590332ms)
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (6.066904ms)
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (3.324998ms)
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (59.308293ms)
✔ CLI: serve rejects a non-numeric --port with exit 2 (55.522575ms)
✔ CLI: scan still works after the async cli refactor (53.850787ms)
✔ CLI: live serve prints its URL and answers /healthz (61.200639ms)
✔ page.html: esc() escapes every HTML-significant character (0.675095ms)
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.234269ms)
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.545656ms)
✔ POST /api/seen with invalid JSON returns 400 (3.060052ms)
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.197926ms)
✔ POST /api/seen with a missing agent returns 400 (1.398132ms)
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.736396ms)
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (53.499758ms)
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.308873ms)
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.265262ms)
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.166295ms)
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.126278ms)
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.310662ms)
✔ page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped (0.56578ms)
✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (30.870217ms)
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (26.792986ms)
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (25.851421ms)
✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (14.635697ms)
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.589091ms)
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.15599ms)
✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (0.54229ms)
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.532864ms)
✔ page.html: the task cell and detail show who set a registered task via setByTag()/setByText(), both escaped, only from rec.taskSetBy; the reply gate does not read it (0.265191ms)
✔ Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers (7.573961ms)
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (38.456176ms)
✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (5.733812ms)
✔ every refusal code the reader can raise has an HTTP status (0.931204ms)
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (46.821522ms)
ℹ tests 124
ℹ suites 0
ℹ pass 124
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 649.810218
@@ -0,0 +1,169 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (209.751905ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (38.333254ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (21.625618ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (402.771722ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (396.446509ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (240.690278ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (226.460251ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (234.896642ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.398274ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (11455.374433ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (29497.79063ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (261.396667ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (147.713723ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (377.884533ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (275.319295ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (339.355963ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (103.909867ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (168.85289ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (257.112717ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (719.077212ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (180.770662ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (92.531202ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (67.979598ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.119897ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.141711ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.340769ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2584.162147ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (492.008614ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2664.877961ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4972.784362ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2624.532286ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2674.914288ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2505.408123ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5120.83543ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (773.580048ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (731.302187ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (478.288361ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (996.568511ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (426.151347ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (558.492406ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3283.844693ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (41.467532ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (349.460793ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (277.967738ms)
✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (83.817843ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (204.0552ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (226.569652ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (200.639765ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (282.434891ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (206.997328ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.451299ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (183.784874ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (329.079159ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (245.587631ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (218.337437ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (274.804399ms)
✔ 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 (282.915848ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.545942ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (256.128477ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (234.961382ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (343.238488ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (246.328666ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.506634ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.2927ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.359291ms)
✔ 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.530604ms)
✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.313817ms)
✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.280818ms)
✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.424211ms)
✔ 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.397178ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.119418ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (234.698348ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (249.331105ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (318.777107ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (803.326944ms)
✔ H4: self-takeover is refused (237.029747ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (492.168032ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1309.455766ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (329.117142ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (678.52757ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (351.993748ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (217.254745ms)
✔ H13: a retry with the same request ID and different text is refused (227.625564ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (631.813088ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (334.100158ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (345.524957ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1328.037967ms)
✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (605.823799ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (921.492468ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (230.650527ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (482.41378ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.719789ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (750.256831ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (672.238142ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (880.196859ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3153.535094ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (711.807279ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.139723ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.86508ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.660465ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.466484ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.487751ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.036768ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (0.859256ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.576809ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.759899ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.209432ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (8.693023ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.576323ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.996735ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.966028ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (2.017387ms)
✔ F9: registrations never add or redirect a root (1.943997ms)
✔ F10: a header cwd naming another project is refused (3.50071ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.83125ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.862495ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.448358ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.302207ms)
✔ F13: compaction is a marker in place, then the retained content (0.741404ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (581.485803ms)
✔ fragments never cut a surrogate pair and keep an empty string (5.298589ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.867231ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.492391ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.057707ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.989865ms)
✔ every page and cursor is a valid CHAT-01 record (775.588337ms)
✔ the engine pin holds for the installed package (1.82016ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (303.853561ms)
✔ 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) (549.826273ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (266.029933ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (507.922278ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (438.769512ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (240.225642ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.028887ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (390.161641ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (225.42443ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (320.411831ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (663.311464ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1925.859529ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (219.287665ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (325.691915ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (212.240769ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (278.3767ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (526.1467ms)
✔ N10: fake conformance (32.836031ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (764.760337ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (380.934245ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (382.303541ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (626.979744ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (493.927018ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (385.580455ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (210.180313ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (584.079529ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (823.131625ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (883.020548ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (624.260054ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (279.042334ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (268.120106ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (262.602992ms)
✔ 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 (332.99312ms)
✔ 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 (270.330557ms)
ℹ tests 161
ℹ suites 0
ℹ pass 161
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 47640.577367
@@ -0,0 +1,31 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3030.316712ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3098.145116ms)
✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4710.171327ms)
✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (251.312525ms)
✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (91.580386ms)
✔ bus refusals map to statuses and never carry a path or a stack (64.76072ms)
✔ humanCall speaks the human transport protocol without blocking the server (2852.574308ms)
✔ busReader takes --business, else the live bus host, else refuses (96.386424ms)
✔ humanCall kills a transport that runs past its timeout (528.134961ms)
✔ serve refuses a --business value that is not a business id (193.493434ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3016.442078ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1770.01472ms)
✔ conversation view: a newer session with no readable history keeps the marker (1138.252882ms)
✔ conversation view: seats without history say so and offer no reply (813.139622ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2929.646577ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53584.87674ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2994.618722ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22249.862961ms)
✔ loopback host and board origin fail closed (8.473866ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (115.009552ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (115.397812ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1711.384212ms)
ℹ tests 22
ℹ suites 0
ℹ pass 22
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 54018.332686
@@ -0,0 +1,14 @@
1 stacked Ctrl-T, Ctrl-O, hi Enter: sent []; composer ""; status "takeover: transferred"
order: observe -> takeover start -> takeover end -> observe
2 takeover throws, via input(): sent []; composer "hi"; status "not admitted: controller"
order: observe -> takeover start -> takeover threw -> call 0 resolved -> call 1 resolved
3 takeover throws, via key(): sent []; composer "hi"; status "not admitted: controller"
order: observe -> takeover start -> takeover threw -> call 0 threw not connected -> call 1 resolved
4 earlier Ctrl-G throws: sent ["hi"]; composer ""; status "prompt: admitted"
order: observe -> interrupt start -> interrupt end -> call 0 threw interrupt broke -> takeover start -> takeover end -> prompt hi start -> prompt hi end -> call 1 resolved -> call 2 resolved
5 paste split across held chunks: sent ["x\ry"]; composer ""; status "prompt: admitted"
order: observe -> takeover start -> takeover end -> prompt x␍y start -> prompt x␍y end
6 input while the drained prompt runs: sent ["a","b"]; composer ""; status "prompt: admitted"
order: observe -> takeover start -> takeover end -> prompt a start -> prompt a end -> prompt b start -> prompt b end
7 Ctrl-C behind a takeover that never answers: quit called after 500 ms: false; held "\u0003"
8 Ctrl-C behind a Ctrl-G that never answers (no hold): quit called after 500 ms: false; held null
@@ -0,0 +1,14 @@
1 stacked Ctrl-T, Ctrl-O, hi Enter: sent ["hi"]; composer ""; status "prompt: admitted"
order: observe -> takeover start -> takeover end -> observe -> prompt hi start -> prompt hi end
2 takeover throws, via input(): sent []; composer "hi"; status "not admitted: controller"
order: observe -> takeover start -> takeover threw -> call 0 resolved -> call 1 resolved
3 takeover throws, via key(): sent []; composer "hi"; status "not admitted: controller"
order: observe -> takeover start -> takeover threw -> call 0 threw not connected -> call 1 resolved
4 earlier Ctrl-G throws: sent ["hi"]; composer ""; status "prompt: admitted"
order: observe -> interrupt start -> interrupt end -> call 0 threw interrupt broke -> takeover start -> takeover end -> prompt hi start -> prompt hi end -> call 1 resolved -> call 2 resolved
5 paste split across held chunks: sent ["x\ry"]; composer ""; status "prompt: admitted"
order: observe -> takeover start -> takeover end -> prompt x␍y start -> prompt x␍y end
6 input while the drained prompt runs: sent ["a","b"]; composer ""; status "prompt: admitted"
order: observe -> takeover start -> takeover end -> prompt a start -> prompt a end -> prompt b start -> prompt b end
7 Ctrl-C behind a takeover that never answers: quit called after 500 ms: false; held "\u0003"
8 Ctrl-C behind a Ctrl-G that never answers (no hold): quit called after 500 ms: false; held null
@@ -0,0 +1,6 @@
separate chunks: sent ["hi"]; composer ""; status "prompt: admitted"
order: interrupt start -> interrupt end -> takeover start -> takeover end -> prompt hi
one chunk after Ctrl-G: sent ["hi"]; composer ""; status "prompt: admitted"
order: interrupt start -> interrupt end -> takeover start -> takeover end -> prompt hi
control: no earlier action: sent ["hi"]; composer ""; status "prompt: admitted"
order: takeover start -> takeover end -> prompt hi
@@ -0,0 +1,4 @@
== base
Ctrl-C behind a hung Ctrl-G: quit called after 500 ms: false
== wt
Ctrl-C behind a hung Ctrl-G: quit called after 500 ms: false
@@ -0,0 +1,2 @@
candidate: sent ["hi","yo"]; held null; quit true
Mr-no-recompute: sent []; held "hi\ryo\r\u0003"; quit false
@@ -0,0 +1,5 @@
Mm: humanCall -> outcome-unknown after 705 ms; hung CLI pid alive after: false
Mp: --business "../acme" -> exit 2: refused: business must be a business id
Mp: --business "-x" -> exit 2: refused: business must be a business id
Mp: --business "acme corp" -> exit 2: refused: business must be a business id
Mp: --business "acme" -> exit 2: refused: port must be 0..65535
@@ -0,0 +1,13 @@
node-webui rc=0 ℹ pass 22 ℹ fail 0 54s
node-conversation rc=0 ℹ pass 161 ℹ fail 0 48s
node-control-board rc=0 ℹ pass 124 ℹ fail 0 0s
test-auth rc=0 OK auth.sh without valid config refuses selftest: 15 passed, 0 failed 1s
test-conductor rc=0 OK invalid policy exits 2 (exit 2) selftest: 17 passed, 0 failed 6s
test-config rc=0 OK failed validation modified nothing selftest: 24 passed, 0 failed 1s
test-discord rc=0 OK service install with USER unset finishes and names the account for lingering discord suite: 66 passed, 0 failed 7s
test-extension-package rc=0 OK nested second entrypoint fails closed extension package selftest: 18 passed, 0 failed 2s
test-foundation rc=0 OK no field of the non-bundle file is echoed selftest: 44 passed, 0 failed 38s
test-queue rc=0 skip queue verify and render --check: this checkout (/home/jwoltje/darkwing-scratch/r40c/wt) is not the queue's canonical root (/mnt/storage/src/mosaic-stack) 33s
test-release rc=0 skip state-machine cases (docker daemon unavailable) selftest: 4 passed, 0 failed 0s
test-task rc=1 OK no agent identity on headless run selftest: 26 passed, 2 failed 2s
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 '86b22cc2e6db3920cecab77e51b47bf9094a6b48'.
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 '86b22cc2e6db3920cecab77e51b47bf9094a6b48'.
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: 1794402 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/r40c/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,85 @@
// Round 3 edges of the Ctrl-T/Ctrl-O hold: stacked holds, a takeover that
// throws (input() and key()), an earlier chunk that throws, a paste split
// across held chunks, input arriving while the drained prompt still runs, and
// Ctrl-C behind a takeover that never answers (with the same Ctrl-C behind a
// hung Ctrl-G for comparison, which has no hold). Imports from the review
// worktree by absolute path (WT).
const WT = process.env.WT;
const { Terminal } = await import(`${WT}/packages/conversation/src/terminal.mjs`);
const tick = (ms = 10) => new Promise((r) => setTimeout(r, ms));
function gate() {
let open;
const p = new Promise((r) => (open = r));
return { p, open };
}
function stub({ takeoverThrows = false, interruptThrows = false, promptGate = null } = {}) {
const sent = [], log = [];
let listener = () => {};
const g = { int: gate(), tk: gate(), obs: gate() };
const s = {
closed: false, connection: { id: "conn-1" }, binding: { state: "active", controllerConnection: "conn-2" }, isController: false,
on: (fn) => (listener = fn),
observe: async () => { log.push("observe"); return { outcome: "refused:channel", refusal: "channel" }; },
prompt: async (t) => {
log.push(`prompt ${t} start`);
if (promptGate && t === "a") await promptGate.p;
log.push(`prompt ${t} end`); sent.push(t);
return { outcome: "admitted", refusal: null, receipt: { id: `r-${t}`, state: "admitted" } };
},
interrupt: async () => {
log.push("interrupt start"); await g.int.p; log.push("interrupt end");
if (interruptThrows) throw new Error("interrupt broke");
return { outcome: "refused:controller", refusal: "controller" };
},
takeover: async () => {
log.push("takeover start"); await g.tk.p;
if (takeoverThrows) { log.push("takeover threw"); throw new Error("not connected"); }
s.isController = true; s.binding = { state: "active", controllerConnection: "conn-1" }; listener({ type: "push", kind: "binding" });
log.push("takeover end"); return { outcome: "transferred", refusal: null };
},
};
return { s, sent, log, g };
}
function report(name, t, x, extra = "") {
console.log(`${name}: sent ${JSON.stringify(x.sent)}; composer ${JSON.stringify(t.composer)}; status ${JSON.stringify(t.status)}${extra}`);
console.log(` order: ${x.log.join(" -> ")}`);
}
{ // 1. Ctrl-T pending, then Ctrl-O, then "hi" Enter, as separate chunks.
const x = stub(); const t = new Terminal({ client: x.s });
const done = ["\x14", "\x0f", "hi\r"].map((c) => t.key(c).catch((e) => x.log.push(`threw ${e.message}`)));
await tick(); x.g.tk.open(); await Promise.all(done); await tick();
report("1 stacked Ctrl-T, Ctrl-O, hi Enter", t, x);
}
for (const via of ["input", "key"]) { // 2, 3. The takeover throws.
const x = stub({ takeoverThrows: true }); const t = new Terminal({ client: x.s });
const done = ["\x14", "hi\r"].map((c, i) => t[via](c).then(() => x.log.push(`call ${i} resolved`), (e) => x.log.push(`call ${i} threw ${e.message}`)));
await tick(); x.g.tk.open(); await Promise.all(done);
report(`${via === "input" ? 2 : 3} takeover throws, via ${via}()`, t, x);
}
{ // 4. An earlier chunk's Ctrl-G throws (key()), then Ctrl-T and hi Enter.
const x = stub({ interruptThrows: true }); const t = new Terminal({ client: x.s });
const done = ["\x07", "\x14", "hi\r"].map((c, i) => t.key(c).then(() => x.log.push(`call ${i} resolved`), (e) => x.log.push(`call ${i} threw ${e.message}`)));
await tick(); x.g.int.open(); await tick(); x.g.tk.open(); await Promise.all(done);
report("4 earlier Ctrl-G throws", t, x);
}
{ // 5. A paste split across held chunks, the start marker cut in two.
const x = stub(); const t = new Terminal({ client: x.s });
const done = ["\x14", "\x1b[20", "0~x\ry\x1b[201~", "\r"].map((c) => t.key(c));
await tick(); x.g.tk.open(); await Promise.all(done);
report("5 paste split across held chunks", t, x);
}
{ // 6. "b" Enter arrives while the drained "a" prompt still runs.
const pg = gate(); const x = stub({ promptGate: pg }); const t = new Terminal({ client: x.s });
const d1 = ["\x14", "a\r"].map((c) => t.key(c));
await tick(); x.g.tk.open(); await tick();
const d2 = t.key("b\r"); await tick(); pg.open(); await Promise.all([...d1, d2]);
report("6 input while the drained prompt runs", t, x);
}
for (const [name, first] of [["7 Ctrl-C behind a takeover that never answers", "\x14"], ["8 Ctrl-C behind a Ctrl-G that never answers (no hold)", "\x07"]]) {
const x = stub(); let quit = false; const t = new Terminal({ client: x.s, onQuit: () => (quit = true) });
t.input(first); t.input("\x03");
await tick(500);
console.log(`${name}: quit called after 500 ms: ${quit}; held ${JSON.stringify(t.held)}`);
}
process.exit(0);
@@ -0,0 +1,35 @@
// Does input held behind a Ctrl-T wait for that takeover when an earlier
// chunk's action is still running? An observer presses Ctrl-G (interrupt,
// slow), then Ctrl-T and "hi" Enter. One key at a time, the takeover runs
// before the Enter is judged, so "hi" is sent. Imports from the review
// worktree by absolute path.
const WT = process.env.WT;
const { Terminal } = await import(`${WT}/packages/conversation/src/terminal.mjs`);
function stub() {
const sent = [], log = [];
let listener = () => {}, openInt, openTk;
const intGate = new Promise((r) => (openInt = r)), tkGate = new Promise((r) => (openTk = r));
const s = {
closed: false, connection: { id: "conn-1" }, binding: { state: "active", controllerConnection: "conn-2" }, isController: false,
on: (fn) => (listener = fn),
observe: async () => ({ outcome: "refused:channel", refusal: "channel" }),
prompt: async (t) => (log.push(`prompt ${t}`), sent.push(t), { outcome: "admitted", refusal: null, receipt: { id: "r1", state: "admitted" } }),
interrupt: async () => { log.push("interrupt start"); await intGate; log.push("interrupt end"); return { outcome: "refused:controller", refusal: "controller" }; },
takeover: async () => {
log.push("takeover start"); await tkGate;
s.isController = true; s.binding = { state: "active", controllerConnection: "conn-1" }; listener({ type: "push", kind: "binding" });
log.push("takeover end"); return { outcome: "transferred", refusal: null };
},
};
return { s, sent, log, openInt, openTk };
}
for (const [name, chunks] of [["separate chunks", ["\x07", "\x14", "hi\r"]], ["one chunk after Ctrl-G", ["\x07", "\x14hi\r"]], ["control: no earlier action", ["\x14", "hi\r"]]]) {
const { s, sent, log, openInt, openTk } = stub();
const term = new Terminal({ client: s });
const done = chunks.map((c) => term.key(c).catch((e) => log.push(`threw ${e.message}`)));
await new Promise((r) => setTimeout(r, 10));
openInt(); await new Promise((r) => setTimeout(r, 10));
openTk(); await Promise.all(done);
console.log(`${name}: sent ${JSON.stringify(sent)}; composer ${JSON.stringify(term.composer)}; status ${JSON.stringify(term.status)}`);
console.log(` order: ${log.join(" -> ")}`);
}
@@ -0,0 +1,10 @@
// Ctrl-C behind a Ctrl-G that never answers, against any terminal.mjs (WT):
// run against the base to show the wait predates the hold.
const { Terminal } = await import(`${process.env.WT}/packages/conversation/src/terminal.mjs`);
let quit = false;
const s = { closed: false, connection: { id: "c1" }, binding: { state: "active", controllerConnection: "c2" }, isController: false, on: () => {}, observe: async () => ({ outcome: "refused:channel", refusal: "channel" }), interrupt: () => new Promise(() => {}) };
const t = new Terminal({ client: s, onQuit: () => (quit = true) });
(t.input ?? t.key).call(t, "\x07"); (t.input ?? t.key).call(t, "\x03");
await new Promise((r) => setTimeout(r, 500));
console.log(`Ctrl-C behind a hung Ctrl-G: quit called after 500 ms: ${quit}`);
process.exit(0);
@@ -0,0 +1,28 @@
// After a stacked hold (Ctrl-T, then Ctrl-O in the held text, then "hi"
// Enter), does later input still run? Types "yo" Enter and Ctrl-C afterwards
// and reports what was sent, `held`, and whether quit was called. Run against
// the candidate and under Mr-no-recompute. Imports from the review worktree
// by absolute path (WT).
const WT = process.env.WT;
const { Terminal } = await import(`${WT}/packages/conversation/src/terminal.mjs`);
const tick = (ms = 10) => new Promise((r) => setTimeout(r, ms));
const sent = [];
let listener = () => {};
const s = {
closed: false, connection: { id: "conn-1" }, binding: { state: "active", controllerConnection: "conn-2" }, isController: false,
on: (fn) => (listener = fn),
observe: async () => ({ outcome: "refused:channel", refusal: "channel" }),
prompt: async (t) => (sent.push(t), { outcome: "admitted", refusal: null, receipt: { id: `r-${t}`, state: "admitted" } }),
takeover: async () => {
s.isController = true; s.binding = { state: "active", controllerConnection: "conn-1" }; listener({ type: "push", kind: "binding" });
return { outcome: "transferred", refusal: null };
},
};
let quit = false;
const t = new Terminal({ client: s, onQuit: () => (quit = true) });
for (const c of ["\x14", "\x0f", "hi\r"]) t.input(c);
await tick(50);
for (const c of ["yo\r", "\x03"]) t.input(c);
await tick(50);
console.log(`sent ${JSON.stringify(sent)}; held ${JSON.stringify(t.held)}; quit ${quit}`);
process.exit(0);
@@ -0,0 +1,26 @@
// Two of the surviving mutants, probed against the candidate. Mm: does
// humanCall kill a human CLI that never answers once the wait runs out? Mp:
// does `serve --business` refuse a value that isn't a business id? Imports
// from the review worktree by absolute path (WT). The bad --port after
// --business stops the CLI before it would start a server.
import { mkdtempSync, readFileSync, writeFileSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { spawnSync } from "node:child_process";
const WT = process.env.WT;
const { humanCall } = await import(`${WT}/packages/webui/src/bus.mjs`);
const root = mkdtempSync(join(tmpdir(), "r40-surv-"));
const cli = join(root, "hang.mjs"), pidFile = join(root, "pid");
writeFileSync(cli, `require("fs").writeFileSync(${JSON.stringify(pidFile)}, String(process.pid)); setInterval(() => {}, 1000);\n`.replace("require(\"fs\")", "(await import('node:fs'))"));
const t0 = Date.now();
const err = await humanCall({ socket: "s", business: "demo", cli, timeoutMs: 500 })("inbox").catch((e) => e);
const pid = Number(readFileSync(pidFile, "utf8"));
await new Promise((r) => setTimeout(r, 200));
let alive = true; try { process.kill(pid, 0); } catch { alive = false; }
console.log(`Mm: humanCall -> ${err.code} after ${Date.now() - t0} ms; hung CLI pid alive after: ${alive}`);
if (alive) process.kill(pid, "SIGKILL");
for (const value of ["../acme", "-x", "acme corp", "acme"]) {
const r = spawnSync(process.execPath, [`${WT}/packages/webui/src/cli.mjs`, "serve", "--business", value, "--port", "99999"], { encoding: "utf8", env: { PATH: process.env.PATH, HOME: root } });
console.log(`Mp: --business ${JSON.stringify(value)} -> exit ${r.status}: ${r.stderr.trim()}`);
}
rmSync(root, { recursive: true, force: true });
@@ -0,0 +1,191 @@
# Row 40, slice 1 S5 (Console bus views and conversation hardening), round 3 review (Darkwing)
Issue #1522, request comment 27007, notes comment 27008, queue rev 257.
Candidate: 27 files, uncommitted in the main checkout, listed in
`r3/files.txt`. Manifest `agents/dewey/work/queue-40/candidate-manifest-r3.sha256`
(copy in `r3/`), sha256
`d0aa0ded00e7d396ff11c651277e1d20b697c526fa831b1d409a665095f1680b`.
Seven files changed since round 2: `terminal.mjs`, `flows.test.mjs`,
`webui/tests/bus.test.mjs`, both READMEs, `DEFERRED.md` and
`s5-mutants.md`. Round 2: my comment 27001 (changes, T1), Filbert's
comment 27005. Filbert approved round 3 in comment 27011 (rev 260).
Verdict: **approve**, comment 27013. T1 is fixed and tested, and
Filbert's N4 holds. My round 2 notes are all dealt with. One new mutant
survives (Mr). The code it changes is right, but nothing tests a hold
inside held text, and under Mr the terminal ignores all later input,
Ctrl-C included. That is note 1, with a test to add. No round 2 copy of
the candidate survives in my scratch, so I reviewed the seven files
against the base, not as an interdiff.
## Method
- A detached worktree at `86b22cc2`. I extracted the 27 candidate files
over it from a tar snapshot of the shared checkout and ran
`sha256sum -c` on the manifest: 27 OK. I symlinked `node_modules` and
ran `git add -N .`. After the probes and mutants I checked again: 27 OK
(`r3/mut/manifest-after.txt`).
- I read `terminal.mjs` in full, the diffs of the other six files against
the base, the new flows and bus tests, Dewey's mutant evidence and
Filbert's comment 27011. For the WebUI README's `--business` claim I
read `packages/cli/src/cli.mjs:75-104`.
- Probes in `r3/probe/` import from my scratch worktree by absolute path
(`WT`), so they won't run as committed without setting it.
`quit-base.mjs` also ran against a second worktree at the base with no
candidate.
- 22 mutants (`r3/mut/mutate.py`, `run.sh`, `all.sh`): round 2's 16, plus
six on the new `#feed`/`#run` code. Each ran its package's node suite,
then the file was restored from a backup copy.
Node is v26.8.1, `TMPDIR=~/darkwing-scratch/tmp`. `gate.sh` set
`DOCKER_HOST=unix:///nonexistent.sock`.
## Suites
| Suite | Result |
|---|---|
| webui | 22/22 |
| conversation | 161/161 |
| control-board | 124/124 |
| 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 | skipped: the worktree isn't the queue's canonical root |
| test-release | 4/0 with Docker blocked; 14/0 run alone with Docker (`out/test-release-docker.txt`) |
| test-task | 26/2: "user recall run succeeds" and "recalled user name" |
The two test-task failures are the live recall, as in round 2: a real Pi
worker in Docker against the zai provider, which would be a paid call.
I didn't make it.
## T1: fixed
`#feed` (`terminal.mjs:121-130`) parses the chunk and records `holds`,
whether this parse set `held`. `#run` (lines 143-161) drains `held` only
when `holds` is set, and recomputes it after each parse of the drained
text. An earlier chunk's run stops after its own actions. A chunk that
arrives while `held` is set is appended and never parsed by itself.
`probe/hold-order.mjs`, unchanged from round 2 (`out/probe-hold-order.txt`):
all three cases send `hi` with status "prompt: admitted", and the two with
a Ctrl-G run interrupt, takeover, then the prompt. The new flows test has
the probe's shape over three chunkings, asserts that `held` is still set
and nothing sent before the takeover opens, and checks the call order.
Mq, which drains `held` from any run as round 2 did, fails it.
The header (`terminal.mjs:17-19`) and the DEFERRED line now say what the
code does.
## N4 and the per-action catch
`#run` catches each action on its own. With `report` (the `input()` path)
it reports the error when it happens, so a later status stands; without
it (`key()`) the first error is rethrown at the end. A held chunk returns
`queue.catch(() => {})`, so its `key()` doesn't carry the holder's error.
`probe/hold-edges.mjs` (`out/probe-hold-edges.txt`), each case read as
one key at a time:
| Case | Result |
|---|---|
| Ctrl-T, Ctrl-O, `hi` Enter as three chunks | `hi` sent after both |
| The takeover throws, via `input()` | nothing sent, `hi` left in the composer, status "not admitted: controller", both calls resolve |
| The same via `key()` | call 0 rejects "not connected", call 1 resolves |
| An earlier Ctrl-G throws | the takeover still runs and `hi` is sent |
| A paste split across held chunks, its start marker cut | `x\ry` sent as one message |
| `b` Enter typed while the drained `a` prompt runs | `a` then `b`, in order |
## Round 2 notes
1. DEFERRED keeps N2 and moves F2 to done citing T1, which is now true.
It is Sage's file, so this is Dewey's proposal for Sage to take or
leave.
2. The conversation README says the seal leaves context files on and that
whoever sets `engine.cwd` chooses them.
3. The WebUI README says the copied `mosaic decide` has no `--business`.
`businessFor` (`cli.mjs:75-79`) takes the running host's business, and
`findDecision` looks only in that business's inbox, so a command copied
from a Console for another business finds nothing and refuses, or
there's no host. The README says that.
4. Mm and Mp now each have a test that fails under the mutant (below).
## Mutants
| Mutant | Change | Result | Killed by |
|---|---|---|---|
| Ma-no-approve | `--no-approve` removed from SEAL_FLAGS | killed | smoke: sealed Pi ignores a trusted project's .pi resources |
| Mb-seal-command | the sealed command check skipped | killed | N24b |
| Mc-engine-keys | an unknown engine key accepted | killed | N24b, N24 |
| Md-env-inherit | `engineEnv` copies the whole source environment | killed | K19, N24b |
| Me-escalating | the fence's catch keeps `escalating` | killed | races: a force stop whose fence throws |
| Mf-queue-catch | `key()` chains without catching | killed | terminal: after an action throws (N1) |
| Mg-no-hold | no action holds input | killed | terminal: N1, F2, T1, N4 |
| Mh-manager-env | the scope gets the engine env without MANAGER_ENV | killed | K19 |
| Mi-bus-post | a non-GET bus request is served | killed | bus routes are GET-only |
| Mj-subject | the trail subject check skipped | killed | bus routes serve the four reads |
| Mk-copy-safe | the copy command offered without SAFE | **survived** | |
| Ml-option-inert | option text rendered raw | killed | S5 pages in a browser |
| Mm-timeout-kill | no SIGKILL when the wait runs out | killed | humanCall kills a transport that runs past its timeout |
| Mn-max-bytes | no `maxBytes` limit | killed | humanCall speaks the human transport protocol |
| Mo-status | `human-required` maps to 502 | killed | bus refusals map to statuses; S5 pages |
| Mp-business-id | `serve --business` takes any value | killed | serve refuses a --business value that is not a business id |
| Mq-drain-any | `#run` drains `held` whoever set it (round 2) | killed | terminal: T1 |
| Mr-no-recompute | `holds` set false after a drained parse | **survived** | |
| Ms-no-try | no per-action catch in `#run` | killed | terminal: N1; a throw still releases held input; N4 |
| Mt-held-rethrow | a held chunk returns `this.queue` uncaught | killed | terminal: N4 |
| Mu-no-report | `input()` errors held to the end, not reported | killed | terminal: N1 |
| Mv-feed-holds-true | every run acts as the one that set `held` | killed | seven tests, among them E1, S4, S5 and T1 |
Twenty of 22 killed. Mk is as in round 2: the broker refuses an option
key that fails its `id()` rule, so the browser check can't be reached
through a real broker.
## Notes (not blocking)
1. **Mr survives, and its failure locks the terminal.** When drained text
holds again (a Ctrl-O or second Ctrl-T inside held input), `#run` must
recompute `holds` (line 158) to keep draining. Under Mr it stops, and
`held` stays set. Nothing clears it, so every later chunk is appended
to `held` and never parsed. `probe/stacked-later.mjs`
(`out/probe-stacked-later.txt`) types Ctrl-T, Ctrl-O, `hi` Enter,
then `yo` Enter and Ctrl-C:
```
candidate: sent ["hi","yo"]; held null; quit true
Mr-no-recompute: sent []; held "hi\ryo\r\u0003"; quit false
```
The candidate is right; Filbert's probe of `\x14a\x14` agrees. But a
one-line regression here would leave the operator unable to type or
quit, and the suite stays green. A flows test with the probe's shape
(a hold inside held text, then later input) would catch it. I'd add it
before landing or as the first follow-up.
2. Ctrl-C and Ctrl-D wait behind any pending action. `client.request()`
has no timeout, and every action, quit included, chains on
`this.queue`. Raw mode means the terminal gets no SIGINT. So behind a
takeover or Ctrl-G the controller never answers, quit doesn't run
(`hold-edges` cases 7 and 8). `probe/quit-base.mjs` gives the same
result on the base, so this predates the hold. A quit path that skips
the queue, or a request timeout, would fix it. That belongs with the
I3 entry point, not this row.
3. Gate E isn't run yet and nothing outside the tests constructs a
Controller, as in round 2. This approval doesn't cover it.
## Files
- `review-r3.md`: this review.
- `r3/files.txt`, `r3/candidate-manifest-r3.sha256`: the candidate as reviewed.
- `r3/gate.sh`, `r3/out/`: the suite runs, `out/summary.txt` first.
- `r3/probe/hold-order.mjs`, `out/probe-hold-order.txt`: T1.
- `r3/probe/hold-edges.mjs`, `out/probe-hold-edges.txt`: the hold cases
above; `out/probe-hold-edges-Mr.txt` is the same run under Mr.
- `r3/probe/stacked-later.mjs`, `out/probe-stacked-later.txt`: note 1.
- `r3/probe/quit-base.mjs`, `out/probe-quit-base.txt`: note 2, base and
candidate.
- `r3/probe/survivors.mjs`, `out/probe-survivors.txt`: Mm and Mp by hand,
kept from round 2.
- `r3/mut/`: `mutate.py`, `run.sh`, `all.sh`, `summary.txt`, one output
per mutant, and `manifest-after.txt` (27).