diff --git a/agents/darkwing/work/s40-review/candidate-manifest-r2.sha256 b/agents/darkwing/work/s40-review/candidate-manifest-r2.sha256 new file mode 100644 index 00000000..8b7a41f4 --- /dev/null +++ b/agents/darkwing/work/s40-review/candidate-manifest-r2.sha256 @@ -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 +3f4c129e7f90fc0840bea48fc9fee44f9eeca212594a26d78bb9fd3aa72933eb agents/dewey/work/wui/evidence/s5-mutants.md +035038082edb22de6a63c64c2dd5ae9ec7372ffb1894ebf270a4de005b06800b docs/plans/DEFERRED.md +cba8267fd7ab6bb7ce2e4259fcc4f5203d77b66821d0d73e962e7a1c60956149 packages/conversation/README.md +38621d505f6ffd859e37c4309f7a2d3a3ee21a53d1ba41b8ff9ca29250a6dc37 packages/conversation/src/cohort.mjs +26f8fa2c7caf794e6224802b8e5d8882419d0bda725d2ab30f0a058a678a57b9 packages/conversation/src/controller.mjs +a06468434e7bf3a3dfc3c5ea05b8c21a1510f8fa0c8eaa6e9908b970fa98367e packages/conversation/src/pi-pin.mjs +72fd7af1475218abcbde15e1801260d6c0db5d3f8c47f09b7aa68e2e47414e0a 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 +b148bb535d6b9727a57feb30b873e84b6a5cc3d849f3c2b0eacf3515b6456d35 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 +2ce037d9cd480709b31fd20f35c72ee81d04398c7532d44f0ecd9905c5fa9b37 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 +c4ef7ad46824ac85b19b64a1003c82e7585f5a5aea60cc4fc06caee68c6066a1 packages/webui/tests/bus.test.mjs diff --git a/agents/darkwing/work/s40-review/files.txt b/agents/darkwing/work/s40-review/files.txt new file mode 100644 index 00000000..183a26e3 --- /dev/null +++ b/agents/darkwing/work/s40-review/files.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/gate.sh b/agents/darkwing/work/s40-review/gate.sh new file mode 100755 index 00000000..5fb1ef66 --- /dev/null +++ b/agents/darkwing/work/s40-review/gate.sh @@ -0,0 +1,13 @@ +#!/bin/bash +cd ~/darkwing-scratch/r40/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 diff --git a/agents/darkwing/work/s40-review/mut/Ma-no-approve-conversation.txt b/agents/darkwing/work/s40-review/mut/Ma-no-approve-conversation.txt new file mode 100644 index 00000000..09feed5f --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Ma-no-approve-conversation.txt @@ -0,0 +1,215 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (215.734217ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (30.090989ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.980164ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (405.386156ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (374.318183ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (240.091944ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (192.837364ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (200.373425ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.247794ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9753.675015ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28405.238599ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (397.68177ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (206.523203ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (413.50365ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (282.600014ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (367.309205ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (184.483417ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (246.472104ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (376.069017ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (769.132946ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (196.401677ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (93.585559ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (81.210759ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.601222ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.069301ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.906818ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2952.064699ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (408.438337ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2710.368338ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4765.598324ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2461.207352ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2431.554867ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2426.758587ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4972.26544ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (830.798916ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (649.094567ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (457.343009ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (714.834561ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (363.544463ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (449.242512ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3226.990201ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (21.931046ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (358.679006ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (319.741504ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (92.571552ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (201.562548ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (190.800832ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (239.813323ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (266.529084ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (194.14995ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.42812ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (197.036991ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (296.310125ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (206.808005ms) +✔ 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.768088ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (207.101203ms) +✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (212.435542ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.600455ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (207.322116ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (226.364403ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (228.214604ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (193.702896ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.387882ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.264308ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.319035ms) +✔ 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.461003ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.46042ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.334847ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.096673ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (221.293496ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (250.648465ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (330.562218ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (787.229564ms) +✔ H4: self-takeover is refused (192.278662ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (421.586417ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1035.200957ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (218.074312ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (644.738076ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (265.478844ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (176.351176ms) +✔ H13: a retry with the same request ID and different text is refused (183.107693ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (544.024946ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (276.417652ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (179.128911ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (806.617395ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (296.643113ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (781.424447ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (199.831614ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (342.352883ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.704008ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (650.602629ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (531.111718ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (887.14627ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3008.033112ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (596.639978ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.764637ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.641242ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.714388ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.442191ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.541866ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.954164ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.985548ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.86867ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.834017ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.021926ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.020142ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.808422ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.43336ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.43153ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (2.503466ms) +✔ F9: registrations never add or redirect a root (2.010541ms) +✔ F10: a header cwd naming another project is refused (3.797972ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.850422ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.213076ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.719771ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.2892ms) +✔ F13: compaction is a marker in place, then the retained content (0.763214ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (552.473465ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.27795ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.514904ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.288278ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.119264ms) +✔ a seat directory without search permission refuses that root, not the catalogue (1.946767ms) +✔ every page and cursor is a valid CHAT-01 record (745.419524ms) +✔ the engine pin holds for the installed package (2.795764ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (309.928745ms) +✖ 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) (282.865427ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (258.878451ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (523.940095ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (407.494234ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (237.379859ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.912765ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (345.240285ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (201.603905ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (285.650341ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (465.982865ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1880.98028ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (179.733316ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (266.134272ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (179.524672ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (201.15844ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (335.715067ms) +✔ N10: fake conformance (32.391103ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (407.037914ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (272.04392ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (275.591711ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (327.502102ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (376.373895ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (379.53785ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (201.832666ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (326.128546ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (653.07212ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (557.478341ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (647.661691ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (230.230467ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (197.294388ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (205.026807ms) +✔ 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 (223.870077ms) +✔ 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 (223.608242ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 158 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 45394.243997 + +✖ 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) (282.865427ms) + 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. (file:///home/jwoltje/darkwing-scratch/r40/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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mb-seal-command-conversation.txt b/agents/darkwing/work/s40-review/mut/Mb-seal-command-conversation.txt new file mode 100644 index 00000000..2069fde1 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mb-seal-command-conversation.txt @@ -0,0 +1,183 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (197.03779ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (29.97602ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (17.555786ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (352.033926ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (390.75591ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (267.856413ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (233.513447ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (233.453037ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.478252ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10100.231669ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28958.101561ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (433.739704ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (155.090042ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (380.413215ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (273.553989ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (340.215515ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (101.794584ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (176.0337ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (262.954923ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (719.88442ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (182.272223ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (85.297948ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (75.006333ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.83732ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.086378ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.957084ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2912.34442ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (466.917263ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2860.450314ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4802.535499ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2542.576909ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2579.387986ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2345.04178ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4955.366077ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (649.392469ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (510.796316ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (412.605052ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (640.124815ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (355.984153ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (464.198228ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3296.679746ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (41.119379ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (371.099757ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (295.684511ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (83.111489ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (192.850763ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (192.39652ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (190.623196ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (282.322192ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (188.384605ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.460758ms) +✔ 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.709668ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (352.168356ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (227.232449ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (201.331356ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (211.051472ms) +✔ 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 (209.300908ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.524658ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (207.326016ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (225.824307ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (253.245799ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (183.629919ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.402648ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.292537ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.313979ms) +✔ 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.478511ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.549054ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.35613ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.106707ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (229.196585ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (243.695057ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (273.202709ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (883.270169ms) +✔ H4: self-takeover is refused (203.835722ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (448.37835ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1018.697608ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (260.023243ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (942.559029ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (329.264518ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (187.90565ms) +✔ H13: a retry with the same request ID and different text is refused (185.737917ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (567.005149ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (283.411765ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (184.54151ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (846.58303ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (345.55871ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (943.931786ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (213.609299ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (343.860754ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.6235ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (670.297241ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (561.154597ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (843.198903ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3047.048478ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (710.567097ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.413692ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.527954ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.557562ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.359374ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.555265ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.028353ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.942922ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.205415ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.455034ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.160185ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.781395ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.290279ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.078973ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.223607ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (1.729329ms) +✔ F9: registrations never add or redirect a root (1.545998ms) +✔ F10: a header cwd naming another project is refused (3.620291ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.641044ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.415534ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.789138ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.2822ms) +✔ F13: compaction is a marker in place, then the retained content (0.688824ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (516.621654ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.000039ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.448373ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.321526ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.046598ms) +✔ a seat directory without search permission refuses that root, not the catalogue (1.887048ms) +✔ every page and cursor is a valid CHAT-01 record (786.047611ms) +✔ the engine pin holds for the installed package (1.751146ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (260.447681ms) +✔ 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) (536.229231ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (263.288171ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (466.411929ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (420.556417ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (226.043858ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.236259ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (416.227922ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (217.403283ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (303.067315ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (479.70206ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1926.323545ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (198.725403ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (259.460009ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (190.678301ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (223.731366ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (375.925123ms) +✔ N10: fake conformance (31.869748ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (363.722004ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (263.081325ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (283.560756ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (367.415706ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (352.779208ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (367.598488ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (209.744524ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (516.089118ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (780.022393ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (554.569781ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (587.382581ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (317.258867ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (299.819146ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (229.222486ms) +✔ 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 (284.282632ms) +✖ 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 (409.522865ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 158 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 45916.46308 + +✖ 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 (409.522865ms) + AssertionError [ERR_ASSERTION]: Missing expected rejection. + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mc-engine-keys-conversation.txt b/agents/darkwing/work/s40-review/mut/Mc-engine-keys-conversation.txt new file mode 100644 index 00000000..e49eb0fe --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mc-engine-keys-conversation.txt @@ -0,0 +1,205 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (214.095609ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (32.345601ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (23.735595ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (351.524729ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (382.804946ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (241.043126ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (198.032439ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (192.073591ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.22368ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9852.701624ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28569.705097ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (258.673948ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (143.122799ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (381.512542ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (274.589497ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (339.816735ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (103.896181ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (169.686307ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (264.287608ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (708.710667ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (193.137923ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (89.60988ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (74.367017ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.377474ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.120036ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.008147ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2971.800289ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (407.700041ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2764.545424ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4827.245221ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2565.568268ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2482.612751ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2357.066443ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4865.368147ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (688.182849ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (551.850357ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (432.364624ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (611.43579ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (333.974321ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (487.726516ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3278.207669ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (24.167348ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (364.029384ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (385.457513ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.643827ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (203.232558ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (206.104347ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (197.937513ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (279.71601ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (199.451891ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.431411ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (201.951872ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (303.058582ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (215.256675ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (167.885224ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (185.48778ms) +✔ 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.589464ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.138366ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (195.564651ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (210.607897ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (233.096676ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (190.11544ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.423484ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.264841ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.325254ms) +✔ 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.448962ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (4.975494ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.378942ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.10795ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (220.696141ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (246.211131ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (293.920118ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (797.551106ms) +✔ H4: self-takeover is refused (194.259238ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (406.913821ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (949.742318ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (172.45756ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (734.159675ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (286.265296ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (179.925063ms) +✔ H13: a retry with the same request ID and different text is refused (152.422798ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (567.960789ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (327.231841ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (302.277385ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1049.972982ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (372.856995ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (896.710868ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (258.952819ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (374.877178ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.648685ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (685.93ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (518.583469ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (679.093781ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3043.848224ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (633.335568ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.511269ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.665091ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.69395ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.53559ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.555307ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.923282ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.777207ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.283103ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.911031ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.93761ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.884498ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.649865ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.999428ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.016961ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (1.949543ms) +✔ F9: registrations never add or redirect a root (1.649819ms) +✔ F10: a header cwd naming another project is refused (3.011248ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.761747ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.141885ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.304809ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.246194ms) +✔ F13: compaction is a marker in place, then the retained content (0.696235ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (543.810968ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.291317ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.722768ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.526017ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.217081ms) +✔ a seat directory without search permission refuses that root, not the catalogue (2.272513ms) +✔ every page and cursor is a valid CHAT-01 record (798.712911ms) +✔ the engine pin holds for the installed package (2.056258ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (285.457126ms) +✔ 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) (543.939009ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (236.447859ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (483.951474ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (438.600149ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (237.148269ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.112248ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (352.447961ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (194.763483ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (282.100367ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (438.162852ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1849.119502ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (151.701627ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (270.339359ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (185.743933ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (219.330312ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (359.946996ms) +✔ N10: fake conformance (32.57973ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (624.636357ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (342.1363ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (294.692455ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (418.611803ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (363.965179ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (477.43516ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (242.623372ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (434.41769ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (698.625644ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (545.441811ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (484.142656ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (215.795047ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (172.761112ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (206.988124ms) +✖ 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.73934ms) +✖ 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.620028ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 157 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 45029.401862 + +✖ 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.73934ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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.620028ms) + AssertionError [ERR_ASSERTION]: Missing expected exception: {"command":"/bin/sh"} + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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' + } diff --git a/agents/darkwing/work/s40-review/mut/Md-env-inherit-conversation.txt b/agents/darkwing/work/s40-review/mut/Md-env-inherit-conversation.txt new file mode 100644 index 00000000..d94ca2a0 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Md-env-inherit-conversation.txt @@ -0,0 +1,206 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (274.192438ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.689842ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.63745ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (366.877011ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (411.519471ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (255.931163ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (194.522528ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (193.476597ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.422065ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10155.345335ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28162.143547ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (251.876892ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (143.568637ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (397.488732ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (277.092965ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (327.790274ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (105.142762ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (165.04157ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (257.465448ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (737.106075ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (242.251611ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (100.426616ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (75.785877ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.111719ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.085039ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.982371ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2955.12095ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (435.280165ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2727.345945ms) +✔ K4: two engines; force stop one; the other survives by independent observation (5008.827865ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2482.574522ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2496.621139ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2375.214718ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4928.028497ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (707.75689ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (547.814058ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (399.271979ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (672.398112ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (337.744689ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (579.693891ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3318.731ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (38.466597ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (387.555766ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (278.546021ms) +✖ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (1.301895ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (256.091094ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (210.352845ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (190.964018ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (297.030046ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (204.39464ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.791872ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (195.961061ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (297.503976ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (210.02514ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (161.981728ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (200.498098ms) +✔ 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.311749ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.131797ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (223.150912ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (256.354182ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (253.807238ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (203.860132ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.43712ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.27438ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.317879ms) +✔ 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.498648ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.54897ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.364944ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.099395ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (232.886748ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (260.300216ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (307.298538ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (820.404169ms) +✔ H4: self-takeover is refused (176.668858ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (420.993534ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1084.515517ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (211.081944ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (678.69221ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (336.763525ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (173.868041ms) +✔ H13: a retry with the same request ID and different text is refused (163.28455ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (606.076245ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (467.101512ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (277.934696ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (941.794372ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (376.499325ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (767.146772ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (196.411932ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (389.350727ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.678811ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (970.710054ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (567.939924ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (725.921342ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3020.57529ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (587.290166ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.51236ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.108435ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.226641ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (4.035531ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.819874ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.538248ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.88837ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.301627ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (4.543424ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (6.959544ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (6.623724ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.926679ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (9.078904ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (10.296415ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (3.15002ms) +✔ F9: registrations never add or redirect a root (2.556755ms) +✔ F10: a header cwd naming another project is refused (4.320427ms) +✔ F11: parentSession renders with a marker and the parent is never opened (1.318759ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (7.620597ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (3.266172ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.474117ms) +✔ F13: compaction is a marker in place, then the retained content (0.86065ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (565.696933ms) +✔ fragments never cut a surrogate pair and keep an empty string (4.899549ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.616405ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.418204ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.128571ms) +✔ a seat directory without search permission refuses that root, not the catalogue (2.100197ms) +✔ every page and cursor is a valid CHAT-01 record (888.735189ms) +✔ the engine pin holds for the installed package (2.150149ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (325.265909ms) +✔ 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) (552.918625ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (304.379471ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (542.190667ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (451.337043ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (234.74496ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.606335ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (352.580901ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (189.427908ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (267.691098ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (488.043922ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1896.623699ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (151.669622ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (260.837621ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (209.82217ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (231.281658ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (513.233476ms) +✔ N10: fake conformance (31.21414ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (618.72897ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (309.097268ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (300.714733ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (376.738117ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (359.591639ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (365.570073ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (208.631343ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (381.470632ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (1076.847028ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (566.105131ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (484.320593ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (214.469601ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (167.90282ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (206.575547ms) +✔ 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 (215.856945ms) +✖ 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.607668ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 157 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 45108.355323 + +✖ 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.301895ms) + AssertionError [ERR_ASSERTION]: XDG_RUNTIME_DIR + + true !== false + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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 (226.607668ms) + AssertionError [ERR_ASSERTION]: NODE_TEST_CONTEXT + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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' + } diff --git a/agents/darkwing/work/s40-review/mut/Me-escalating-conversation.txt b/agents/darkwing/work/s40-review/mut/Me-escalating-conversation.txt new file mode 100644 index 00000000..949a241e --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Me-escalating-conversation.txt @@ -0,0 +1,189 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (252.01439ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (31.489523ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.531414ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (366.215517ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (385.352696ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (231.941266ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (210.492776ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (180.157794ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.279785ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9729.362328ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28655.546457ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (250.617713ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (150.579649ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (380.336628ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (267.082472ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (336.957538ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (101.924094ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (168.9722ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (260.781709ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (725.285009ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (177.208335ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (87.331132ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (70.816601ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.571417ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.11596ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.734854ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2956.430273ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (409.614268ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2713.875705ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4772.108131ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2485.76993ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2530.682957ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2414.026802ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5008.08648ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (742.611065ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (572.755392ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (434.882205ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (690.023686ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (388.402734ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (487.574544ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3414.405647ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (117.200394ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (484.087662ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (634.720767ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.525567ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (193.32442ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (204.610977ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (239.457405ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (287.773019ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (197.173178ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.413161ms) +✔ 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.287718ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (287.179408ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (228.510474ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (179.814703ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (206.257005ms) +✔ 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.046065ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.599427ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (202.075965ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (214.050858ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (245.59034ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (198.757365ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.397968ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.266243ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.31703ms) +✔ 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.489405ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.548117ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.373164ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.102205ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (222.830689ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (290.058098ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (296.338706ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (798.243374ms) +✔ H4: self-takeover is refused (208.279821ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (423.421109ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (993.866535ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (196.155859ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (632.245802ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (321.916355ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (191.520939ms) +✔ H13: a retry with the same request ID and different text is refused (169.200574ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (591.108818ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (278.13812ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (202.312162ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (878.598068ms) +✖ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (196.845024ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (729.917774ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (252.641858ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (462.178583ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.523346ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (726.36511ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (600.861984ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (703.121301ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2945.075111ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (639.989098ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.679214ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.469022ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.544347ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.358444ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.620177ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.829453ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.722721ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.23306ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.213235ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.709143ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.977509ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.606156ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.067208ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.759639ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (2.041497ms) +✔ F9: registrations never add or redirect a root (1.458751ms) +✔ F10: a header cwd naming another project is refused (3.160638ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.75834ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.253724ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.226052ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.195277ms) +✔ F13: compaction is a marker in place, then the retained content (0.734529ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (506.591055ms) +✔ fragments never cut a surrogate pair and keep an empty string (4.979408ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.61534ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.373404ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.124348ms) +✔ a seat directory without search permission refuses that root, not the catalogue (2.131518ms) +✔ every page and cursor is a valid CHAT-01 record (755.026327ms) +✔ the engine pin holds for the installed package (1.692423ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (257.158334ms) +✔ 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.821012ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (255.164389ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (522.214615ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (429.438467ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (241.03602ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (20.698651ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (336.598921ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (207.273528ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (294.178409ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (464.491287ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1869.316966ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (146.982051ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (280.041726ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (194.504097ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (230.775729ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (361.427403ms) +✔ N10: fake conformance (31.716838ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (409.329857ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (273.861818ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (294.53304ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (359.416715ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (370.163451ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (325.826136ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (232.25554ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (491.732564ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (875.906995ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (620.599674ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (497.151605ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (208.605161ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (178.75738ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (156.790084ms) +✔ 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 (232.495923ms) +✔ 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 (206.189095ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 158 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 45275.981581 + +✖ 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) (196.845024ms) + AssertionError [ERR_ASSERTION]: the flag is cleared on the throw + + actual - expected + + + 'stop-559e6aca9b4abf45' + - null + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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-559e6aca9b4abf45', + expected: null, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s40-review/mut/Mf-queue-catch-conversation.txt b/agents/darkwing/work/s40-review/mut/Mf-queue-catch-conversation.txt new file mode 100644 index 00000000..04dc13f1 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mf-queue-catch-conversation.txt @@ -0,0 +1,183 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (208.379184ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (32.708965ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.722303ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (367.079952ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (441.831479ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (245.123658ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (189.196636ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (249.210469ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.222024ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9941.485877ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28925.986271ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (271.97681ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (145.588987ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (388.140706ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (308.571438ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (360.624944ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (104.346068ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (180.472853ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (264.795244ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (739.344879ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (411.085238ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (174.028461ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (69.586299ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.468875ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.087087ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.767224ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2947.908413ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (408.226994ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2685.443746ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4798.337116ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2529.760773ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2512.012952ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2385.803026ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5033.678846ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (769.956365ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (549.920737ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (425.141876ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (604.023087ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (358.564082ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (449.67424ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3296.129692ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (29.468267ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (329.987821ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (295.713833ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (81.312792ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (192.150114ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (204.217747ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (197.72356ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (355.282622ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (200.773264ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.41759ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (196.15806ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (290.302122ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (288.111081ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (213.499099ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (210.614328ms) +✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (211.424395ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.706419ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (208.422337ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (225.76497ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (231.058219ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (198.744301ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.356207ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.24642ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.325815ms) +✔ 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.446664ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.502883ms) +✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.33097ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.102764ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (238.257033ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (257.085596ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (299.470685ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (859.099561ms) +✔ H4: self-takeover is refused (276.975406ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (467.86615ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1011.461007ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (190.080357ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (663.247233ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (287.512063ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (193.629884ms) +✔ H13: a retry with the same request ID and different text is refused (173.814805ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (586.425474ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (300.488496ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (212.724974ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (897.548461ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (366.981315ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (780.998686ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (316.194283ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (451.056112ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.595911ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (771.973126ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (547.094588ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (689.545133ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2971.626798ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (617.550263ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.705826ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.517099ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.439911ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.143675ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.501837ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.733036ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.683251ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.082298ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.074295ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.571199ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.22585ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.434529ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.179928ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.194749ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (1.512069ms) +✔ F9: registrations never add or redirect a root (1.425923ms) +✔ F10: a header cwd naming another project is refused (3.689045ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.698645ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.44308ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.742653ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.19793ms) +✔ F13: compaction is a marker in place, then the retained content (0.700578ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (510.072972ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.051947ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.531414ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.438218ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.101352ms) +✔ a seat directory without search permission refuses that root, not the catalogue (1.956673ms) +✔ every page and cursor is a valid CHAT-01 record (771.646559ms) +✔ the engine pin holds for the installed package (1.666342ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (257.584691ms) +✔ 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.755669ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (273.964511ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (476.056493ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (521.50693ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (240.131647ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.882703ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (343.565958ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (274.046792ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (343.965533ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (491.17782ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1832.644239ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (174.790434ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (278.310176ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (192.452376ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (227.414947ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (352.357668ms) +✔ N10: fake conformance (31.920062ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (416.377276ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (290.413117ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (297.164374ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (377.402401ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (360.14ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (345.048973ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (246.784167ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (554.479285ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (920.620683ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (548.884553ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (513.57861ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (199.649854ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (173.852295ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (198.324393ms) +✔ 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.703598ms) +✔ 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 (234.614341ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 158 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 45906.210956 + +✖ failing tests: + +test at packages/conversation/tests/flows.test.mjs:695:1 +✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.33097ms) + Error: not connected + at stub.takeover (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/tests/flows.test.mjs:701:28) + at #act (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/src/terminal.mjs:206:80) + at file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/src/terminal.mjs:193:37 + at #run (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/src/terminal.mjs:132:42) + at file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/src/terminal.mjs:111:49 + at async waitForActual (node:assert:615:5) + at async strict.rejects (node:assert:738:25) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/tests/flows.test.mjs:705:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) diff --git a/agents/darkwing/work/s40-review/mut/Mg-no-hold-conversation.txt b/agents/darkwing/work/s40-review/mut/Mg-no-hold-conversation.txt new file mode 100644 index 00000000..f2a46598 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mg-no-hold-conversation.txt @@ -0,0 +1,212 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (296.879466ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.602049ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.988722ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (634.77033ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (453.736192ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (252.635706ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (207.837078ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (246.981625ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.20387ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10701.199275ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28622.102803ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (255.657875ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (142.962745ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (373.96892ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (273.544328ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (343.973996ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (106.330855ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (177.406761ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (272.637294ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (727.233609ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (195.085291ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (97.562888ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (72.519482ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.174078ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.070535ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.848447ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3269.439191ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (444.526081ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2779.973182ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4990.707418ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2602.780023ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2523.308505ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2355.186127ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5053.411674ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (714.496723ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (564.706305ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (577.380937ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (661.878683ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (366.946461ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (557.442049ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3290.493335ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (24.581982ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (376.593608ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (271.967232ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (82.577497ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (298.450359ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (219.778174ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (435.308373ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (377.306704ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (208.797517ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.421764ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (226.260973ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (325.624776ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (254.701092ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (249.170151ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (225.932826ms) +✔ 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.512549ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.773179ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (229.954779ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (241.043249ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (268.968065ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (221.082673ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.391363ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.275444ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.318012ms) +✖ 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.048883ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.38194ms) +✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.417655ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.108076ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (226.913325ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (263.394406ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (569.967493ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (905.563084ms) +✔ H4: self-takeover is refused (234.183058ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (517.222561ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1125.986535ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (219.559426ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (716.109466ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (333.659671ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (216.405515ms) +✔ H13: a retry with the same request ID and different text is refused (198.95763ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (642.435178ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (319.532164ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (225.456339ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (978.006103ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (468.487843ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (1216.293202ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (235.150364ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (373.41183ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.694791ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (692.751101ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (557.237066ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (726.214256ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3075.482023ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (614.452576ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.735959ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.850053ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.901921ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.66378ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.606899ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.120974ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.794561ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.319727ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.680396ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.255977ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.929874ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.998962ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.120789ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.957622ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (1.729244ms) +✔ F9: registrations never add or redirect a root (1.649047ms) +✔ F10: a header cwd naming another project is refused (4.348808ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.780672ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.913409ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.097609ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.327085ms) +✔ F13: compaction is a marker in place, then the retained content (0.81907ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (557.254154ms) +✔ fragments never cut a surrogate pair and keep an empty string (4.98921ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.683243ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.338516ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.071919ms) +✔ a seat directory without search permission refuses that root, not the catalogue (1.93729ms) +✔ every page and cursor is a valid CHAT-01 record (834.056181ms) +✔ the engine pin holds for the installed package (2.722366ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (268.4409ms) +✔ 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) (540.509377ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (287.689775ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (611.026628ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (764.388387ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (264.467228ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.700682ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (387.715704ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (238.933065ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (359.666641ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (522.709088ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1904.764961ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (210.184241ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (302.764591ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (210.628946ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (252.47441ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (373.828393ms) +✔ N10: fake conformance (32.105439ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (459.654921ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (316.01937ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (303.206479ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (472.412874ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (428.917428ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (699.491437ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (282.746565ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (418.366953ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (822.805927ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (583.639915ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (439.183085ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (225.307202ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (250.532768ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (249.623854ms) +✔ 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 (286.109959ms) +✔ 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.012875ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 157 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 46457.536737 + +✖ 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.048883ms) + AssertionError [ERR_ASSERTION]: text typed after the takeover is sent + + actual - expected + + + [] + - [ + - 'hi' + - ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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:695:1 +✖ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.417655ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + + [] + - [ + - 'hi' + - ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/tests/flows.test.mjs:710: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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mh-manager-env-conversation.txt b/agents/darkwing/work/s40-review/mut/Mh-manager-env-conversation.txt new file mode 100644 index 00000000..edc7c2f5 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mh-manager-env-conversation.txt @@ -0,0 +1,180 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (211.092981ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.733864ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.932717ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (375.797497ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (472.496544ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (268.375073ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (246.287921ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (275.581255ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.69412ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10336.184958ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28721.630848ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (425.153978ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (160.074522ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (440.44504ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (305.543379ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (404.469792ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (109.073704ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (173.031065ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (277.457478ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (805.018807ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (176.510769ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (94.845238ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (78.852164ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.141458ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.101538ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.719039ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3010.438173ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (430.9459ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2757.697662ms) +✔ K4: two engines; force stop one; the other survives by independent observation (5268.048614ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2519.614996ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2517.187317ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2319.730115ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4843.065359ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (660.454044ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (525.144915ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (420.896538ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (612.547653ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (407.607433ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (481.50494ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3276.206316ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (39.317371ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (346.907107ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (330.257916ms) +✖ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (2.884999ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (201.115283ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (211.570095ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (214.702081ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (352.014843ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (194.613313ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.679594ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (196.906002ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (338.928015ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (278.039565ms) +✔ 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.97132ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (219.305896ms) +✔ 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.376273ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.963602ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (202.905996ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (221.073745ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (239.463364ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (202.961559ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.394422ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.257447ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.313652ms) +✔ 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.054752ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.917936ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.363857ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.096568ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (231.352165ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (275.481809ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (382.020762ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (927.184305ms) +✔ H4: self-takeover is refused (227.307647ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (468.290263ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1000.172793ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (213.939092ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (685.167323ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (349.076018ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (199.478283ms) +✔ H13: a retry with the same request ID and different text is refused (188.729884ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (805.941736ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (298.547727ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (259.606662ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1251.319396ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (511.769523ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (815.430781ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (202.535553ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (352.883403ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.711028ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (614.292922ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (558.283096ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (883.075575ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2899.722981ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (633.046632ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.73204ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.524952ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.523678ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.322127ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.578563ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.83161ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.72625ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.183773ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.200172ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.520068ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.497767ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.36033ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.294169ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.372157ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (1.608869ms) +✔ F9: registrations never add or redirect a root (1.500659ms) +✔ F10: a header cwd naming another project is refused (3.640168ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.640996ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.366858ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (1.657602ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.226544ms) +✔ F13: compaction is a marker in place, then the retained content (0.681958ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (519.484827ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.317123ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.597311ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.391469ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.143826ms) +✔ a seat directory without search permission refuses that root, not the catalogue (2.048682ms) +✔ every page and cursor is a valid CHAT-01 record (792.211447ms) +✔ the engine pin holds for the installed package (1.628098ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (247.561343ms) +✔ 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) (538.434901ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (322.058507ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (494.848905ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (514.150432ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (230.188919ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.012945ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (406.680056ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (276.494037ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (315.427162ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (477.595467ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1888.222153ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (177.586665ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (276.824034ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (196.082661ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (342.81011ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (498.763098ms) +✔ N10: fake conformance (32.883332ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (538.082625ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (313.846605ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (473.666685ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (501.069651ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (519.887083ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (375.125152ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (217.500393ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (386.398391ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (692.048111ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (555.910759ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (436.527312ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (198.540286ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (208.571146ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (145.604125ms) +✔ 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 (180.784214ms) +✔ 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 (172.227126ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 158 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 46344.419184 + +✖ 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.884999ms) + Error: the scope exited before the shim answered ({"code":1,"signal":null}) + at ScopeLauncher.launch (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/conversation/src/cohort.mjs:113:45) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mi-bus-post-webui.txt b/agents/darkwing/work/s40-review/mut/Mi-bus-post-webui.txt new file mode 100644 index 00000000..d60dc4aa --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mi-bus-post-webui.txt @@ -0,0 +1,49 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3221.628233ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4263.755917ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5119.347359ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (252.958033ms) +✖ bus routes are GET-only, have no write verb, and keep the same-origin checks (14.662257ms) +✔ bus refusals map to statuses and never carry a path or a stack (65.376398ms) +✔ humanCall speaks the human transport protocol without blocking the server (2449.7778ms) +✔ busReader takes --business, else the live bus host, else refuses (165.763824ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3079.004277ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2476.049333ms) +✔ conversation view: a newer session with no readable history keeps the marker (1210.712061ms) +✔ conversation view: seats without history say so and offer no reply (851.102727ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4024.752782ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53895.302206ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4236.558657ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21613.242497ms) +✔ loopback host and board origin fail closed (8.09759ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (77.928439ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (45.822306ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1338.880388ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 19 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 54187.939054 + +✖ failing tests: + +test at packages/webui/tests/bus.test.mjs:88:1 +✖ bus routes are GET-only, have no write verb, and keep the same-origin checks (14.662257ms) + AssertionError [ERR_ASSERTION]: POST + + 200 !== 405 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/webui/tests/bus.test.mjs:94: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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mj-subject-webui.txt b/agents/darkwing/work/s40-review/mut/Mj-subject-webui.txt new file mode 100644 index 00000000..3e79c350 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mj-subject-webui.txt @@ -0,0 +1,54 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3073.071578ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3189.998512ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (3868.811802ms) +✖ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (234.334354ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (95.409477ms) +✔ bus refusals map to statuses and never carry a path or a stack (97.94629ms) +✔ humanCall speaks the human transport protocol without blocking the server (2892.139712ms) +✔ busReader takes --business, else the live bus host, else refuses (108.562253ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3141.473487ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1490.775134ms) +✔ conversation view: a newer session with no readable history keeps the marker (1058.979921ms) +✔ conversation view: seats without history say so and offer no reply (796.20488ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2633.758473ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53209.783204ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2979.733343ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22024.39633ms) +✔ loopback host and board origin fail closed (9.045907ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (96.127964ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (85.806072ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1487.792561ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 19 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 53607.640805 + +✖ failing tests: + +test at packages/webui/tests/bus.test.mjs:47:1 +✖ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (234.334354ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + '', + + 'invalid-response' + - 'invalid-request' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/webui/tests/bus.test.mjs:83: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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mk-copy-safe-webui.txt b/agents/darkwing/work/s40-review/mut/Mk-copy-safe-webui.txt new file mode 100644 index 00000000..a0c8dcec --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mk-copy-safe-webui.txt @@ -0,0 +1,29 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3803.143211ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4150.99418ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5513.812675ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (293.93593ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (118.151082ms) +✔ bus refusals map to statuses and never carry a path or a stack (75.472187ms) +✔ humanCall speaks the human transport protocol without blocking the server (2747.214028ms) +✔ busReader takes --business, else the live bus host, else refuses (144.717044ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3841.377464ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1741.194052ms) +✔ conversation view: a newer session with no readable history keeps the marker (1126.155233ms) +✔ conversation view: seats without history say so and offer no reply (820.523132ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4036.515655ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53920.32752ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4020.396638ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22048.307214ms) +✔ loopback host and board origin fail closed (7.477996ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (94.638418ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (78.017455ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1504.625696ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 20 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 54272.538263 diff --git a/agents/darkwing/work/s40-review/mut/Ml-option-inert-webui.txt b/agents/darkwing/work/s40-review/mut/Ml-option-inert-webui.txt new file mode 100644 index 00000000..fd0be9f5 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Ml-option-inert-webui.txt @@ -0,0 +1,50 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3903.591086ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4463.702877ms) +✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (4518.981424ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (279.019779ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (159.736142ms) +✔ bus refusals map to statuses and never carry a path or a stack (58.470909ms) +✔ humanCall speaks the human transport protocol without blocking the server (2488.423873ms) +✔ busReader takes --business, else the live bus host, else refuses (249.447116ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4493.182334ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1537.330523ms) +✔ conversation view: a newer session with no readable history keeps the marker (1091.121132ms) +✔ conversation view: seats without history say so and offer no reply (786.66719ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4450.612777ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54196.285951ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4245.335109ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21797.08746ms) +✔ loopback host and board origin fail closed (7.038814ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (77.894089ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (43.066275ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1046.053579ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 19 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 54466.262774 + +✖ 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 (4518.981424ms) + AssertionError [ERR_ASSERTION]: decision + + 2 !== 0 + + at inertCheck (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/webui/tests/bus-browser.test.mjs:79:12) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mm-timeout-kill-webui.txt b/agents/darkwing/work/s40-review/mut/Mm-timeout-kill-webui.txt new file mode 100644 index 00000000..6472057a --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mm-timeout-kill-webui.txt @@ -0,0 +1,29 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4264.624684ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4915.776488ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5506.106252ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (302.974369ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (164.739853ms) +✔ bus refusals map to statuses and never carry a path or a stack (301.289466ms) +✔ humanCall speaks the human transport protocol without blocking the server (2336.450303ms) +✔ busReader takes --business, else the live bus host, else refuses (146.099874ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4159.684661ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2486.788222ms) +✔ conversation view: a newer session with no readable history keeps the marker (1379.50928ms) +✔ conversation view: seats without history say so and offer no reply (888.468817ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3816.895237ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54704.988605ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4035.41478ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22152.655801ms) +✔ loopback host and board origin fail closed (6.827332ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (71.673018ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (41.736535ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1256.131211ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 20 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 61908.886122 diff --git a/agents/darkwing/work/s40-review/mut/Mn-max-bytes-webui.txt b/agents/darkwing/work/s40-review/mut/Mn-max-bytes-webui.txt new file mode 100644 index 00000000..48f346bb --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mn-max-bytes-webui.txt @@ -0,0 +1,52 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3371.478092ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3533.766582ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5236.546106ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (265.403013ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (218.700345ms) +✔ bus refusals map to statuses and never carry a path or a stack (77.297046ms) +✖ humanCall speaks the human transport protocol without blocking the server (987.672419ms) +✔ busReader takes --business, else the live bus host, else refuses (338.959782ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3347.152936ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2067.554963ms) +✔ conversation view: a newer session with no readable history keeps the marker (1139.050408ms) +✔ conversation view: seats without history say so and offer no reply (846.901724ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3092.438272ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53731.708381ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3145.795761ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22022.168906ms) +✔ loopback host and board origin fail closed (9.381138ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (92.056158ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (76.38614ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1449.133125ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 19 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 54092.092299 + +✖ failing tests: + +test at packages/webui/tests/bus.test.mjs:146:1 +✖ humanCall speaks the human transport protocol without blocking the server (987.672419ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + actual - expected + + + 'resolved' + - 'response-too-large' + ^ + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/webui/tests/bus.test.mjs:154: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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mo-status-webui.txt b/agents/darkwing/work/s40-review/mut/Mo-status-webui.txt new file mode 100644 index 00000000..cc8bed13 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mo-status-webui.txt @@ -0,0 +1,64 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4078.025266ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4229.110588ms) +✖ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (10593.488837ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (278.884475ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (126.572093ms) +✖ bus refusals map to statuses and never carry a path or a stack (19.130718ms) +✔ humanCall speaks the human transport protocol without blocking the server (2702.097792ms) +✔ busReader takes --business, else the live bus host, else refuses (451.218777ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4085.471214ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1874.890628ms) +✔ conversation view: a newer session with no readable history keeps the marker (1186.036497ms) +✔ conversation view: seats without history say so and offer no reply (1060.494636ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4043.986396ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54810.893037ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4042.638709ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21982.440883ms) +✔ loopback host and board origin fail closed (6.288103ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (92.036631ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (112.29199ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1394.064416ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 18 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 55233.638745 + +✖ 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 (10593.488837ms) + 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/r40/wt/packages/webui/tests/browser.mjs:44:35) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r40/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:104:1 +✖ bus refusals map to statuses and never carry a path or a stack (19.130718ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + 'human-required', + + 502, + - 403, + 'human-required' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r40/wt/packages/webui/tests/bus.test.mjs:115: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' + } diff --git a/agents/darkwing/work/s40-review/mut/Mp-business-id-webui.txt b/agents/darkwing/work/s40-review/mut/Mp-business-id-webui.txt new file mode 100644 index 00000000..c833ab88 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/Mp-business-id-webui.txt @@ -0,0 +1,29 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4692.208792ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (5111.336662ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5354.257314ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (292.823804ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (88.625651ms) +✔ bus refusals map to statuses and never carry a path or a stack (45.995245ms) +✔ humanCall speaks the human transport protocol without blocking the server (2413.458142ms) +✔ busReader takes --business, else the live bus host, else refuses (204.764008ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (4308.079893ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1993.341774ms) +✔ conversation view: a newer session with no readable history keeps the marker (1187.251099ms) +✔ conversation view: seats without history say so and offer no reply (760.485733ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4301.813666ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54092.039906ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4340.487534ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22566.802883ms) +✔ loopback host and board origin fail closed (8.882657ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (103.083919ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (91.974333ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1290.735571ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 20 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 54463.595654 diff --git a/agents/darkwing/work/s40-review/mut/all.sh b/agents/darkwing/work/s40-review/mut/all.sh new file mode 100644 index 00000000..dda08bed --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/all.sh @@ -0,0 +1,6 @@ +#!/bin/bash +cd ~/darkwing-scratch/r40/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; 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-r2.sha256 2>&1 | grep -c ': OK$' > ../mut/manifest-after.txt +echo DONE >> ../mut/summary.txt diff --git a/agents/darkwing/work/s40-review/mut/manifest-after.txt b/agents/darkwing/work/s40-review/mut/manifest-after.txt new file mode 100644 index 00000000..f64f5d8d --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/manifest-after.txt @@ -0,0 +1 @@ +27 diff --git a/agents/darkwing/work/s40-review/mut/mutate.py b/agents/darkwing/work/s40-review/mut/mutate.py new file mode 100644 index 00000000..5a5ae6f4 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/mutate.py @@ -0,0 +1,27 @@ +# mutate.py : 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"), +} +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) diff --git a/agents/darkwing/work/s40-review/mut/run.sh b/agents/darkwing/work/s40-review/mut/run.sh new file mode 100644 index 00000000..151163c8 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# run.sh : back up, mutate, run the package's node suite, restore from the backup. +set -u +cd ~/darkwing-scratch/r40/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 diff --git a/agents/darkwing/work/s40-review/mut/summary.txt b/agents/darkwing/work/s40-review/mut/summary.txt new file mode 100644 index 00000000..57a85398 --- /dev/null +++ b/agents/darkwing/work/s40-review/mut/summary.txt @@ -0,0 +1,17 @@ +Ma-no-approve (packages/conversation/src/pi-pin.mjs): conversation rc 1 pass 158 fail 1 +Mb-seal-command (packages/conversation/src/controller.mjs): conversation rc 1 pass 158 fail 1 +Mc-engine-keys (packages/conversation/src/controller.mjs): conversation rc 1 pass 157 fail 2 +Md-env-inherit (packages/conversation/src/pi-pin.mjs): conversation rc 1 pass 157 fail 2 +Me-escalating (packages/conversation/src/controller.mjs): conversation rc 1 pass 158 fail 1 +Mf-queue-catch (packages/conversation/src/terminal.mjs): conversation rc 1 pass 158 fail 1 +Mg-no-hold (packages/conversation/src/terminal.mjs): conversation rc 1 pass 157 fail 2 +Mh-manager-env (packages/conversation/src/cohort.mjs): conversation rc 1 pass 158 fail 1 +Mi-bus-post (packages/webui/src/serve.mjs): webui rc 1 pass 19 fail 1 +Mj-subject (packages/webui/src/serve.mjs): webui rc 1 pass 19 fail 1 +Mk-copy-safe (packages/webui/src/public/bus.js): webui rc 0 pass 20 fail 0 +Ml-option-inert (packages/webui/src/public/bus.js): webui rc 1 pass 19 fail 1 +Mm-timeout-kill (packages/webui/src/bus.mjs): webui rc 0 pass 20 fail 0 +Mn-max-bytes (packages/webui/src/bus.mjs): webui rc 1 pass 19 fail 1 +Mo-status (packages/webui/src/bus.mjs): webui rc 1 pass 18 fail 2 +Mp-business-id (packages/webui/src/cli.mjs): webui rc 0 pass 20 fail 0 +DONE diff --git a/agents/darkwing/work/s40-review/out/node-control-board.txt b/agents/darkwing/work/s40-review/out/node-control-board.txt new file mode 100644 index 00000000..bab52cb5 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/node-control-board.txt @@ -0,0 +1,132 @@ +✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (11.766822ms) +✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.449002ms) +✔ completed smoke replies and ordinary questions are idle, not human blockers (0.828459ms) +✔ only an explicit first-line input request makes a finished reply waiting (0.202427ms) +✔ tool activity, user text, errors and unfinished turns override attention text (0.133911ms) +✔ STOP access failure is unknown, not absence, under a non-root identity (32.685194ms) +✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (2.670289ms) +✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.280195ms) +✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (6.060715ms) +✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.528774ms) +✔ server rescans connector discovery and refuses HTTP reply without transport (34.353982ms) +✔ connector session links and linked directories are not read (1.053549ms) +✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.540764ms) +✔ CLI print uses the relaunch notice instead of old current preview (61.017629ms) +✔ connector owner and fixed task never inherit a native relaunch notice (1.847241ms) +✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.006472ms) +✔ loadConfig: missing file throws ConfigError (1.439638ms) +✔ loadConfig: invalid JSON throws ConfigError (0.321928ms) +✔ loadConfig: missing dataRoot throws ConfigError (0.225256ms) +✔ loadConfig: relative dataRoot throws ConfigError (0.220504ms) +✔ loadConfig: valid config returns dataRoot (0.673442ms) +✔ findNewestSession: picks the newest by mtime among two files (0.484012ms) +✔ findNewestSession: finds files in nested subdirectories (0.308315ms) +✔ findNewestSession: returns null for a missing dir (0.138032ms) +✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.794291ms) +✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.325106ms) +✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.522182ms) +✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.102793ms) +✔ deriveState: full state table (0.169978ms) +✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.375548ms) +✔ rule: newest entry is a tool result with no assistant text after it is working (0.337094ms) +✔ rule: a finished ordinary turn is idle, even if it says your move (0.301326ms) +✔ task: the first user message of the session, from text blocks (0.294023ms) +✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.295016ms) +✔ task: no user message in the log means null (shown as unknown), never a guess (0.381194ms) +✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.431789ms) +✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.539949ms) +✔ scan: the written record carries task, workspace and activeProject (0.523678ms) +✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.703112ms) +✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.441665ms) +✔ 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 (1.15204ms) +✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.465846ms) +✔ loadRegistrations: a missing seatsDir gives empty lists (0.191089ms) +✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.775236ms) +✔ 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.356809ms) +✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (1.16833ms) +✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.263286ms) +✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.551571ms) +✔ scanAgent: ageSeconds is computed from the injected now (0.286943ms) +✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.192608ms) +✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.374537ms) +✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.37833ms) +✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.95233ms) +✔ scan: relative boardDir throws ConfigError (0.086953ms) +✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (57.489098ms) +✔ CLI: missing config exits 2 with a refused: message (50.110867ms) +✔ CLI: unknown command exits 2 (49.880162ms) +✔ CLI: unknown --liveness value exits 2 (49.007096ms) +✔ panesRunPi: true when any trimmed line equals 'pi' (0.216076ms) +✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.094031ms) +✔ tmuxIsAlive: a pane running pi is alive (0.17491ms) +✔ tmuxIsAlive: session exists but pi has exited is not alive (0.072056ms) +✔ tmuxIsAlive: no such tmux session is not alive (0.04995ms) +✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.059709ms) +✔ tmuxIsAlive: passes -L only when a socket is given (0.091196ms) +✔ parsePanes: one pane per line, command and optional tab-separated path (0.075129ms) +✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.070568ms) +✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.087198ms) +✔ loadSeen: missing file returns {} (0.17823ms) +✔ loadSeen: invalid JSON throws ConfigError (0.170346ms) +✔ loadSeen: a JSON array throws ConfigError (0.150123ms) +✔ loadSeen: a non-string value throws ConfigError (0.182105ms) +✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.304833ms) +✔ markSeen: seen false deletes the key (0.253293ms) +✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.207156ms) +✔ markSeen: project containing '/' throws ConfigError (0.114109ms) +✔ markSeen: non-boolean seen throws ConfigError (0.103073ms) +✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.277892ms) +✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.223624ms) +✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.213001ms) +✔ scanAgent: an error-state session with a matching mark is seen (0.226756ms) +✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.484707ms) +✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.183023ms) +✔ 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.620208ms) +✔ 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.558063ms) +✔ 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.923967ms) +✔ isLoopbackHost: recognizes loopback hosts (1.171912ms) +✔ isLoopbackHost: rejects non-loopback hosts (4.533566ms) +✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.534117ms) +✔ startServer: serves page, healthz, and a rescanning /api/board (33.621069ms) +✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (5.65756ms) +✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.235975ms) +✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (50.191509ms) +✔ CLI: serve rejects a non-numeric --port with exit 2 (48.933961ms) +✔ CLI: scan still works after the async cli refactor (46.774006ms) +✔ CLI: live serve prints its URL and answers /healthz (72.151216ms) +✔ page.html: esc() escapes every HTML-significant character (1.320292ms) +✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (17.358298ms) +✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (4.982676ms) +✔ POST /api/seen with invalid JSON returns 400 (2.463228ms) +✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (3.043078ms) +✔ POST /api/seen with a missing agent returns 400 (2.670007ms) +✔ POST /api/board returns 405; PUT /api/seen returns 405 (2.513603ms) +✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (65.117838ms) +✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.344723ms) +✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.279548ms) +✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.164434ms) +✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.193962ms) +✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.31428ms) +✔ 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.600716ms) +✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S :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 (31.701551ms) +✔ POST /api/reply: a registration with a tmux socket adds -L (33.178205ms) +✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (29.032398ms) +✔ 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.509987ms) +✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.553737ms) +✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.157795ms) +✔ 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 (1.17309ms) +✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.615908ms) +✔ 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.294448ms) +✔ 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 (8.579537ms) +✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (33.724672ms) +✔ 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.455968ms) +✔ every refusal code the reader can raise has an HTTP status (0.921898ms) +✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (47.081144ms) +ℹ tests 124 +ℹ suites 0 +ℹ pass 124 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 682.030237 diff --git a/agents/darkwing/work/s40-review/out/node-conversation.txt b/agents/darkwing/work/s40-review/out/node-conversation.txt new file mode 100644 index 00000000..81a259c1 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/node-conversation.txt @@ -0,0 +1,167 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (210.726364ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (34.368141ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (20.018785ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (380.116232ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (456.060011ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (252.098137ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (206.94115ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (187.991244ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.341587ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10487.08231ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (30391.183101ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (255.457824ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (150.488448ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (386.197697ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (272.276158ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (345.385932ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (105.058156ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (166.619923ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (252.611695ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (740.641729ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (187.106016ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (90.148273ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (76.106326ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.392358ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.227629ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.737675ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2974.632634ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (433.906647ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2772.275713ms) +✔ K4: two engines; force stop one; the other survives by independent observation (5113.127761ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2539.623106ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2560.679576ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2428.224863ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5090.323954ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (794.901897ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (760.551247ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (569.400905ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (822.500321ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (786.660593ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (1027.13246ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3314.913825ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (30.623643ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (695.708044ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (619.768071ms) +✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (83.105052ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (210.228801ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (203.250995ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (201.506341ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (365.393559ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (173.007345ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.626767ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (215.491813ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (313.157374ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (212.947715ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (186.839278ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (206.56574ms) +✔ 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 (206.280951ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.72526ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (211.076426ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (219.018188ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (243.642242ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (217.491619ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.433314ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.267288ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.307547ms) +✔ 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.472234ms) +✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.570073ms) +✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.417176ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.106956ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (271.901516ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (251.43548ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (291.514657ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (891.996436ms) +✔ H4: self-takeover is refused (183.681359ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (423.972439ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1042.755354ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (241.650368ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (713.711676ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (281.441201ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (202.4407ms) +✔ H13: a retry with the same request ID and different text is refused (204.470699ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (589.915885ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (304.148401ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (217.646302ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (1111.126289ms) +✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (367.956082ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (1118.722686ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (315.641387ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (501.842991ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.654734ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (670.364645ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (565.368884ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (804.860882ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2924.807529ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (647.650662ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (5.978617ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.740327ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.639434ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.479043ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.582358ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.873284ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.778796ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (2.149743ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.111216ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (3.490035ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.288928ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.055357ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (6.392195ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.138011ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (1.759151ms) +✔ F9: registrations never add or redirect a root (1.35958ms) +✔ F10: a header cwd naming another project is refused (2.853238ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.68484ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (3.959658ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.092588ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.204255ms) +✔ F13: compaction is a marker in place, then the retained content (0.68482ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (554.100134ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.303574ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.553846ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.459406ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.147763ms) +✔ a seat directory without search permission refuses that root, not the catalogue (1.986648ms) +✔ every page and cursor is a valid CHAT-01 record (802.071262ms) +✔ the engine pin holds for the installed package (1.948631ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (266.986593ms) +✔ 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) (575.722151ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (254.170702ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (490.701233ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (513.244073ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (207.007401ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.083634ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (377.539865ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (188.745974ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (294.322787ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (471.836066ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1873.555156ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (201.858805ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (296.58971ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (193.331941ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (203.021271ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (374.483019ms) +✔ N10: fake conformance (32.042935ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (451.519119ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (304.56146ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (486.046883ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (369.30683ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (492.532654ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (533.463148ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (330.30135ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (608.441051ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (668.203481ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (575.61075ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (535.682213ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (216.918194ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (204.71571ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (214.452163ms) +✔ 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.014139ms) +✔ 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 (225.951441ms) +ℹ tests 159 +ℹ suites 0 +ℹ pass 159 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 47611.360851 diff --git a/agents/darkwing/work/s40-review/out/node-webui.txt b/agents/darkwing/work/s40-review/out/node-webui.txt new file mode 100644 index 00000000..28173df3 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/node-webui.txt @@ -0,0 +1,29 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (4036.351556ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4466.950246ms) +✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5115.863809ms) +✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (276.002468ms) +✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (164.156539ms) +✔ bus refusals map to statuses and never carry a path or a stack (142.726233ms) +✔ humanCall speaks the human transport protocol without blocking the server (2558.479321ms) +✔ busReader takes --business, else the live bus host, else refuses (182.557938ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3892.459033ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2071.286884ms) +✔ conversation view: a newer session with no readable history keeps the marker (1118.968483ms) +✔ conversation view: seats without history say so and offer no reply (801.668669ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (4439.861056ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54030.58281ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (4574.368627ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21722.491531ms) +✔ loopback host and board origin fail closed (8.669555ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (111.97983ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (95.364647ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1521.573248ms) +ℹ tests 20 +ℹ suites 0 +ℹ pass 20 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 54445.987031 diff --git a/agents/darkwing/work/s40-review/out/probe-hold-order.txt b/agents/darkwing/work/s40-review/out/probe-hold-order.txt new file mode 100644 index 00000000..c27cc2cb --- /dev/null +++ b/agents/darkwing/work/s40-review/out/probe-hold-order.txt @@ -0,0 +1,6 @@ +separate chunks: sent []; composer ""; status "takeover: transferred" + order: interrupt start -> interrupt end -> takeover start -> takeover end +one chunk after Ctrl-G: sent []; composer ""; status "takeover: transferred" + order: interrupt start -> interrupt end -> takeover start -> takeover end +control: no earlier action: sent ["hi"]; composer ""; status "prompt: admitted" + order: takeover start -> takeover end -> prompt hi diff --git a/agents/darkwing/work/s40-review/out/probe-survivors.txt b/agents/darkwing/work/s40-review/out/probe-survivors.txt new file mode 100644 index 00000000..226ac288 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/probe-survivors.txt @@ -0,0 +1,5 @@ +Mm: humanCall -> outcome-unknown after 706 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 diff --git a/agents/darkwing/work/s40-review/out/summary.txt b/agents/darkwing/work/s40-review/out/summary.txt new file mode 100644 index 00000000..2400ead9 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/summary.txt @@ -0,0 +1,13 @@ +node-webui rc=0 ℹ pass 20 ℹ fail 0 54s +node-conversation rc=0 ℹ pass 159 ℹ 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 5s +test-config rc=0 OK failed validation modified nothing selftest: 24 passed, 0 failed 2s +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 1s +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/r40/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 diff --git a/agents/darkwing/work/s40-review/out/test-auth.txt b/agents/darkwing/work/s40-review/out/test-auth.txt new file mode 100644 index 00000000..ac228760 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-auth.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/out/test-conductor.txt b/agents/darkwing/work/s40-review/out/test-conductor.txt new file mode 100644 index 00000000..c60320a0 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-conductor.txt @@ -0,0 +1,55 @@ +Note: switching to 'f19787ee609cd303f9b4c5a15fc9cac52a50cb4e'. + +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 + +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 'f19787ee609cd303f9b4c5a15fc9cac52a50cb4e'. + +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 + +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 diff --git a/agents/darkwing/work/s40-review/out/test-config.txt b/agents/darkwing/work/s40-review/out/test-config.txt new file mode 100644 index 00000000..76c05ef7 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-config.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/out/test-discord.txt b/agents/darkwing/work/s40-review/out/test-discord.txt new file mode 100644 index 00000000..b013f9ff --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-discord.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/out/test-extension-package.txt b/agents/darkwing/work/s40-review/out/test-extension-package.txt new file mode 100644 index 00000000..b1f73c44 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-extension-package.txt @@ -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: 546752 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 diff --git a/agents/darkwing/work/s40-review/out/test-foundation.txt b/agents/darkwing/work/s40-review/out/test-foundation.txt new file mode 100644 index 00000000..6338e1e6 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-foundation.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/out/test-queue.txt b/agents/darkwing/work/s40-review/out/test-queue.txt new file mode 100644 index 00000000..58cfc787 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-queue.txt @@ -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/r40/wt) is not the queue's canonical root (/mnt/storage/src/mosaic-stack) + +queue suite: 27 passed, 0 failed diff --git a/agents/darkwing/work/s40-review/out/test-release-rerun.txt b/agents/darkwing/work/s40-review/out/test-release-rerun.txt new file mode 100644 index 00000000..7202b85d --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-release-rerun.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/out/test-release.txt b/agents/darkwing/work/s40-review/out/test-release.txt new file mode 100644 index 00000000..1d6ed050 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-release.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/out/test-task.txt b/agents/darkwing/work/s40-review/out/test-task.txt new file mode 100644 index 00000000..85aa46f0 --- /dev/null +++ b/agents/darkwing/work/s40-review/out/test-task.txt @@ -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 diff --git a/agents/darkwing/work/s40-review/probe/hold-order.mjs b/agents/darkwing/work/s40-review/probe/hold-order.mjs new file mode 100644 index 00000000..99297fad --- /dev/null +++ b/agents/darkwing/work/s40-review/probe/hold-order.mjs @@ -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(" -> ")}`); +} diff --git a/agents/darkwing/work/s40-review/probe/survivors.mjs b/agents/darkwing/work/s40-review/probe/survivors.mjs new file mode 100644 index 00000000..bc4efa26 --- /dev/null +++ b/agents/darkwing/work/s40-review/probe/survivors.mjs @@ -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 }); diff --git a/agents/darkwing/work/s40-review/review-r2.md b/agents/darkwing/work/s40-review/review-r2.md new file mode 100644 index 00000000..42e7c6d7 --- /dev/null +++ b/agents/darkwing/work/s40-review/review-r2.md @@ -0,0 +1,204 @@ +# Row 40, slice 1 S5 (Console bus views and conversation hardening), round 2 review (Darkwing) + +Issue #1522, request comment 26996, notes comment 26997, queue rev 249. +Candidate: 27 files, uncommitted in the main checkout, listed in +`files.txt`. Manifest `agents/dewey/work/queue-40/candidate-manifest-r2.sha256` +(copy here), sha256 +`5cbf22c0b25914802cd574ae53fc02aaf0a5bec731f45735f867474372100623`. +Round 1 review: Filbert, comment 26993. + +Verdict: **changes**, comment 27001. One finding, T1. The terminal's input hold, which +answers Filbert F2 from #1507, fails when an earlier chunk's action is +still running. A Ctrl-T typed then drops the text behind it with no +message, so the F2 property the header and DEFERRED claim is false in +that case. Nothing wrong is ever sent. The rest of round 2 holds under my +reading, probes and mutants: the README's Console-process boundary, +`--no-approve` in SEAL_FLAGS, the queue surviving a throw, and the bus +read path. + +## Method + +- A detached worktree at `f19787ee`. I extracted the 27 candidate files + over it 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 (`mut/manifest-after.txt`). +- I read `public/bus.js` in full, plus `serve.mjs`, `bus.mjs`, `cli.mjs`, + the conversation diffs (`pi-pin.mjs`, `controller.mjs`, `cohort.mjs`, + `terminal.mjs`), the new tests, both READMEs, the DEFERRED changes, + `S5-GATE-E.md` and Dewey's mutant evidence. I checked Filbert's comment + 26993 against each change. For `--no-approve` I read the pinned Pi + 0.85.1's `main.js` (the trust override) and the resource loader. +- Probes in `probe/` import from my scratch worktree by absolute path + (`WT`), so they won't run as committed without setting it. +- Sixteen mutants of my own (`mut/mutate.py`, `mut/run.sh`). Each ran the + node suite of its package, then the file was restored from a backup + copy, since the candidate isn't committed in the worktree. + +Node is v26.8.1, `TMPDIR=~/darkwing-scratch/tmp`. `gate.sh` set +`DOCKER_HOST=unix:///nonexistent.sock`, which matters for two suites +below. + +## Suites + +| Suite | Result | +|---|---| +| webui | 20/20 | +| conversation | 159/159 | +| 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-rerun.txt`) | +| test-task | 26/2: "user recall run succeeds" and "recalled user name" | + +The two test-task failures are a live recall: `run-task.sh` runs a real +Pi worker in Docker against the zai provider. With Docker blocked they +can't run, and with Docker they'd be a paid model call, which I didn't +make. They say nothing about this candidate, but I can't confirm Dewey's +"every test-*.sh green" for them. + +## T1: input held behind a Ctrl-T is drained by an earlier chunk's run (blocking) + +`packages/conversation/src/terminal.mjs:102-140, 194-195`. `#parse` puts +the rest of a chunk after Ctrl-T or Ctrl-O into `this.held`. `#run` +drains `this.held` after its own actions finish, whichever chunk set it. +When chunk 1's action is still running (a slow Ctrl-G interrupt), chunk 2 +with Ctrl-T sets `held` at parse time. Chunk 1's `#run` then finishes, +finds `held`, and parses and runs it before chunk 2's takeover has started. +The Enter is judged as an observer and refused (`#take`, line 236), which +leaves the text in the composer. The takeover then clears the composer on +the control transfer (line 83), so the text is gone, and its status +replaces the refusal: the line shows only "takeover: transferred". + +`probe/hold-order.mjs`, an observer pressing Ctrl-G, Ctrl-T, then "hi" +and Enter (`out/probe-hold-order.txt`): + +``` +separate chunks: sent []; composer ""; status "takeover: transferred" + order: interrupt start -> interrupt end -> takeover start -> takeover end +one chunk after Ctrl-G: sent []; composer ""; status "takeover: transferred" + order: interrupt start -> interrupt end -> takeover start -> takeover end +control: no earlier action: sent ["hi"]; composer ""; status "prompt: admitted" + order: takeover start -> takeover end -> prompt hi +``` + +The order line doesn't show the refused Enter, because the stub's +`prompt` is never reached. It is refused inside the terminal before that. + +The header (`terminal.mjs:18`) says input after Ctrl-T or Ctrl-O "waits +until that finishes, in the same chunk or a later one", and DEFERRED moves +Filbert F2 to done. Both are true only when no earlier action is pending. +Pressing Ctrl-G and then Ctrl-T to take control is the natural sequence +for an observer who wants to stop a turn, so this isn't a contrived order. + +Fix: drain `held` only in the run whose chunk set it. For example, +`key()` records whether its own `#parse` set `held` and passes that flag +to `#run`, and `#run` recomputes it after each `#parse` of the drained +text. Add a test with the probe's shape: an earlier action that's still +pending, then Ctrl-T and Enter, both as separate chunks and as one. + +## What holds + +- **Console-process boundary (Filbert F1).** The WebUI README now says the + bus's ancestry proof ends at the Console process. Anything that can + connect to port 7330 gets reader-level reads, including T3 seats and S6 + sessions on the same UID. Reads only. That matches the code: `serve.mjs` + checks Host (loopback plus port) and Origin before every route, the bus + routes are GET-only (405 otherwise, HEAD included), and `busRead` checks + the trail subject before it asks the bus. +- **`--no-approve` in SEAL_FLAGS.** In Pi 0.85.1 `main.js:570-581`, + `--no-approve` makes `projectTrusted` false whatever `trust.json` says. + That gates the project's SYSTEM.md, APPEND_SYSTEM.md (resource loader + 809-830), settings and skills. The new real-Pi smoke test checks the + sealed case against a trusted project, its `--approve` control offers + `skill:probe`, and `checkSeal` refuses `--approve` and `-a`. +- **Engine seal.** `controller.mjs` accepts only `{extraArgs, cwd, + envKeys}` (`UNSEALED_ENGINE` otherwise, line 171), requires + `process.execPath` with the pinned Pi entry when sealed, and checks at + construction and at bind. `engineEnv` builds the environment from + ENGINE_ENV plus envKeys names that match the credential pattern. +- **Queue after a throw (Filbert N1).** `key()` chains on + `this.queue.catch(() => {})`, and `input()` puts the error in the status + line. Both are covered. +- **Force-stop escalation.** The catch around the fence clears + `escalating` (line 712), covered by the races test. +- **Bus read path.** `humanCall` kills the human CLI on timeout and on + `maxBytes`, maps bus error codes to statuses (403 for refusals of this + caller, 503 for no answer, 502 otherwise), and never returns a path or a + stack. `public/bus.js` routes every bus string through `esc`, `inert` + or `txt`. The copy command is offered only for an open, human-routed + decision whose id and key both pass SAFE, the broker's own identifier + rule, so no shell metacharacter or leading dash can reach it. + +## 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: F2, N1 | +| 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 | **survived** | | +| 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 | **survived** | | + +Thirteen of 16 killed. For the three survivors I checked the code by hand: + +- Mk can't be reached through a real broker. `broker.mjs:542` refuses an + option key that fails `id()`, the same rule as SAFE, so the browser + check is a second layer, and the browser test seeds a real broker. A test + would need a stubbed `views` that returns a bad key. +- Mm: `probe/survivors.mjs` runs `humanCall` against a CLI that never + answers, with a 500 ms wait: `outcome-unknown after 706 ms; hung CLI pid + alive after: false` (the 706 includes a 200 ms pause before the check). +- Mp: the same probe runs `serve --business` with `../acme`, `-x` and + `acme corp`, and each exits 2 with "business must be a business id". + `acme` passes and stops at the bad port given after it, before any + server starts. + +The code is right in all three; no test holds Mm or Mp. A `humanCall` +test with a hanging CLI and a `cli.mjs` test with a bad `--business` would. + +## Notes (not blocking) + +1. `docs/plans/DEFERRED.md` is Sage's file, and the candidate edits it. + The N2 entry is right. The line that moves Filbert F2 to done should + wait for T1. +2. SEAL_FLAGS leaves context files on. Under the seal, Pi still loads + AGENTS.md and CLAUDE.md (and their variants, `resource-loader.js:33`) + from the working directory and its parents. + They are prompt text, not code, and a human conversation may want them. + But Pi has `--no-context-files`, the worker adapter uses it, and neither + README says the conversation engine doesn't. One line would settle it. +3. The copied command is `mosaic decide ` with no `--business`. + When the Console runs with `--business X` and the live host serves Y, + the command goes to Y, which refuses an unknown decision. That fails + closed, so this is a note only. +4. The cohort scope still passes `DBUS_SESSION_BUS_ADDRESS` to the engine + (`cohort.mjs:106`). The README already says so. +5. Gate E (`S5-GATE-E.md`) isn't run yet, and nothing outside the tests + constructs a Controller. Expected at this stage; I mention it only so + the round 2 approval, when it comes, isn't read as covering it. + +## Files + +- `review-r2.md`: this review. +- `files.txt`, `candidate-manifest-r2.sha256`: the candidate as reviewed. +- `gate.sh`, `out/`: the suite runs, `out/summary.txt` first. +- `probe/hold-order.mjs`, `out/probe-hold-order.txt`: T1. +- `probe/survivors.mjs`, `out/probe-survivors.txt`: Mm and Mp. +- `mut/`: `mutate.py`, `run.sh`, `all.sh`, `summary.txt`, one output per + mutant, and `manifest-after.txt` (27).