From 974da6edd5f3dc6f5194007aa51f4883ace0fc9b Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 9 Oct 2026 22:19:02 -0500 Subject: [PATCH] docs(review): row 41 round 3 review packet, changes (darkwing) R4 holds. R5: a relative Pi path resolves against the given workspace in the gate and against the real path in Pi, so a workspace on a symlink lets .. reach outside. Comment 27032, queue rev 271. Co-Authored-By: Claude Opus 5.5 --- .../s6-review/r3/candidate-manifest.sha256 | 42 +++ agents/darkwing/work/s6-review/r3/files.txt | 42 +++ agents/darkwing/work/s6-review/r3/gate.sh | 14 + .../work/s6-review/r3/interdiff.patch | 318 ++++++++++++++++++ .../s6-review/r3/mut/MA-pi-only-harness.txt | 85 +++++ .../r3/mut/MB-no-real-base-harness.txt | 85 +++++ .../r3/mut/MC-no-nfd-curly-harness.txt | 103 ++++++ .../s6-review/r3/mut/Ma-late-signals-cli.txt | 115 +++++++ .../r3/mut/Ma-late-signals-harness.txt | 124 +++++++ .../r3/mut/Mb-runs-set-early-bus.txt | 102 ++++++ .../r3/mut/Mb-runs-set-early-cli.txt | 93 +++++ .../s6-review/r3/mut/Mc-no-run-ended-bus.txt | 102 ++++++ .../s6-review/r3/mut/Mc-no-run-ended-cli.txt | 125 +++++++ .../r3/mut/Md-no-instance-running-cli.txt | 138 ++++++++ .../s6-review/r3/mut/Me-no-authorize-cli.txt | 113 +++++++ .../r3/mut/Mf-no-exit2-wrapper-harness.txt | 86 +++++ .../r3/mut/Mg-no-founder-check-cli.txt | 93 +++++ .../r3/mut/Mg-no-founder-check-harness.txt | 97 ++++++ .../r3/mut/Mh-no-close-sigkill-cli.txt | 102 ++++++ .../r3/mut/Mi-recover-no-kill-cli.txt | 115 +++++++ .../r3/mut/Mj-no-stdin-cap-harness.txt | 82 +++++ .../r3/mut/Mk-launch-line-max-cli.txt | 102 ++++++ .../r3/mut/Ml-gate-pattern-harness.txt | 85 +++++ .../s6-review/r3/mut/Mm-no-revoke-bus.txt | 102 ++++++ .../s6-review/r3/mut/Mm-no-revoke-cli.txt | 93 +++++ .../r3/mut/Mn-recover-no-end-cli.txt | 131 ++++++++ .../r3/mut/Mo-policy-outside-try-harness.txt | 91 +++++ .../s6-review/r3/mut/Mp-any-reply-cli.txt | 111 ++++++ .../work/s6-review/r3/mut/Mq-no-timer-cli.txt | 99 ++++++ .../r3/mut/Mr-ignore-unsolicited-cli.txt | 114 +++++++ .../r3/mut/Ms-follow-walk-harness.txt | 125 +++++++ .../r3/mut/Mt-no-socket-destroy-cli.txt | 102 ++++++ .../r3/mut/Mu-no-restricted-harness.txt | 105 ++++++ .../work/s6-review/r3/mut/Mv-no-tag-bus.txt | 102 ++++++ .../work/s6-review/r3/mut/Mv-no-tag-cli.txt | 278 +++++++++++++++ .../s6-review/r3/mut/Mw-ampm-case-harness.txt | 61 ++++ .../r3/mut/Mx-curly-once-harness.txt | 61 ++++ .../r3/mut/My-lstat-error-skips-harness.txt | 61 ++++ .../r3/mut/Mz-spell-no-normalise-harness.txt | 61 ++++ agents/darkwing/work/s6-review/r3/mut/all.sh | 33 ++ .../work/s6-review/r3/mut/manifest-after.txt | 42 +++ .../darkwing/work/s6-review/r3/mut/mutate.py | 73 ++++ .../darkwing/work/s6-review/r3/mut/parse.sh | 8 + .../darkwing/work/s6-review/r3/mut/parse.txt | 87 +++++ agents/darkwing/work/s6-review/r3/mut/run.sh | 19 ++ .../work/s6-review/r3/mut/summary.txt | 30 ++ .../work/s6-review/r3/mut/survivors.sh | 13 + .../work/s6-review/r3/out/node-bus.txt | 82 +++++ .../work/s6-review/r3/out/node-business.txt | 68 ++++ .../work/s6-review/r3/out/node-cli.txt | 93 +++++ .../work/s6-review/r3/out/node-harness.txt | 61 ++++ .../work/s6-review/r3/out/node-seat.txt | 35 ++ .../r3/out/probe-claude-cwd-dotdot.txt | 12 + .../r3/out/probe-pi-cwd-dotdot-r2.txt | 13 + .../s6-review/r3/out/probe-pi-cwd-dotdot.txt | 13 + .../s6-review/r3/out/probe-pi-variant.txt | 16 + .../r3/out/probe-spell-edges-survivors.txt | 18 + .../s6-review/r3/out/probe-spell-edges.txt | 56 +++ .../work/s6-review/r3/out/summary.txt | 15 + .../work/s6-review/r3/out/test-auth.txt | 17 + .../work/s6-review/r3/out/test-conductor.txt | 55 +++ .../work/s6-review/r3/out/test-config.txt | 26 ++ .../work/s6-review/r3/out/test-discord.txt | 70 ++++ .../r3/out/test-extension-package.txt | 21 ++ .../work/s6-review/r3/out/test-foundation.txt | 53 +++ .../work/s6-review/r3/out/test-queue.txt | 35 ++ .../s6-review/r3/out/test-release-docker.txt | 16 + .../work/s6-review/r3/out/test-release.txt | 7 + .../work/s6-review/r3/out/test-task.txt | 33 ++ .../s6-review/r3/probe/claude-cwd-dotdot.sh | 46 +++ .../work/s6-review/r3/probe/mock-api.mjs | 45 +++ .../work/s6-review/r3/probe/pi-cwd-dotdot.sh | 56 +++ .../work/s6-review/r3/probe/pi-variant.sh | 46 +++ .../work/s6-review/r3/probe/spell-edges.mjs | 51 +++ agents/darkwing/work/s6-review/review-r3.md | 279 +++++++++++++++ 75 files changed, 5703 insertions(+) create mode 100644 agents/darkwing/work/s6-review/r3/candidate-manifest.sha256 create mode 100644 agents/darkwing/work/s6-review/r3/files.txt create mode 100755 agents/darkwing/work/s6-review/r3/gate.sh create mode 100644 agents/darkwing/work/s6-review/r3/interdiff.patch create mode 100644 agents/darkwing/work/s6-review/r3/mut/MA-pi-only-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/MB-no-real-base-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/MC-no-nfd-curly-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-bus.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-bus.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Md-no-instance-running-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Me-no-authorize-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mf-no-exit2-wrapper-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mh-no-close-sigkill-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mi-recover-no-kill-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mj-no-stdin-cap-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mk-launch-line-max-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Ml-gate-pattern-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-bus.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mn-recover-no-end-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mo-policy-outside-try-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mp-any-reply-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mq-no-timer-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mr-ignore-unsolicited-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Ms-follow-walk-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mt-no-socket-destroy-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mu-no-restricted-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-bus.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mw-ampm-case-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mx-curly-once-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/My-lstat-error-skips-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/Mz-spell-no-normalise-harness.txt create mode 100755 agents/darkwing/work/s6-review/r3/mut/all.sh create mode 100644 agents/darkwing/work/s6-review/r3/mut/manifest-after.txt create mode 100644 agents/darkwing/work/s6-review/r3/mut/mutate.py create mode 100755 agents/darkwing/work/s6-review/r3/mut/parse.sh create mode 100644 agents/darkwing/work/s6-review/r3/mut/parse.txt create mode 100755 agents/darkwing/work/s6-review/r3/mut/run.sh create mode 100644 agents/darkwing/work/s6-review/r3/mut/summary.txt create mode 100755 agents/darkwing/work/s6-review/r3/mut/survivors.sh create mode 100644 agents/darkwing/work/s6-review/r3/out/node-bus.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/node-business.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/node-cli.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/node-harness.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/node-seat.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/probe-claude-cwd-dotdot.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot-r2.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/probe-pi-variant.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/probe-spell-edges-survivors.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/probe-spell-edges.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/summary.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-auth.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-conductor.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-config.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-discord.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-extension-package.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-foundation.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-queue.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-release-docker.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-release.txt create mode 100644 agents/darkwing/work/s6-review/r3/out/test-task.txt create mode 100644 agents/darkwing/work/s6-review/r3/probe/claude-cwd-dotdot.sh create mode 100644 agents/darkwing/work/s6-review/r3/probe/mock-api.mjs create mode 100644 agents/darkwing/work/s6-review/r3/probe/pi-cwd-dotdot.sh create mode 100755 agents/darkwing/work/s6-review/r3/probe/pi-variant.sh create mode 100644 agents/darkwing/work/s6-review/r3/probe/spell-edges.mjs create mode 100644 agents/darkwing/work/s6-review/review-r3.md diff --git a/agents/darkwing/work/s6-review/r3/candidate-manifest.sha256 b/agents/darkwing/work/s6-review/r3/candidate-manifest.sha256 new file mode 100644 index 00000000..c8ddc66d --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/candidate-manifest.sha256 @@ -0,0 +1,42 @@ +863640ee44598a5bffd6d37d328dcddfd4bdc671f792d029ae2ee18b1055b28b adapters/README.md +8121e4e05b0559471e0d44fc0325fb08c8a883ab3db1bca451a956753cccdfe3 adapters/claude/adapter.sh +962255271e95cb30788e97cd9e86e17f384dd5f74cead435692e8b30e47af9cf adapters/pi/adapter.sh +009059ea86e1d14c5b12ed0fdad64e8cd501e19021ef6f4148a1463d55860125 docs/TOOLS.md +49dc3cf603358fdbce768faaa9ebe8b5e4359a1aa813c3ffeef0d96a01d37035 packages/bus/README.md +aa71088d656455de83d9c1a42745852041ee61e2d5eb6f4c1e48e8ae29623433 packages/bus/src/broker.mjs +0b51592777d2b035e79e2864d061615e81591bf99d43820ea9b3e52f8cf56559 packages/bus/src/process.mjs +12634ff6b6ef5b5a282bb306b30c9f02929d1fb6597e149d47d21069201399b5 packages/bus/src/runtime.mjs +5b06f799e18deba2ee2eb737322f0dcfdd4a8eeae8c92e465f5acdbb894483dd packages/bus/tests/end-launch.test.mjs +e5e41c8bef670daac0507d860f7104c446c736a3897d247cbacb5ab36b440d41 packages/cli/README.md +bd207b81a2b9965b9e46da66ab31ed9a587b87e8669e8293184d4b842e45bff5 packages/cli/src/cli.mjs +e9eeec4bef3384b5b15d1e7a2c9d913d2f3e329228c3e3e2cacb8f741aa21379 packages/cli/src/host.mjs +2d0b075982f295a88161607d2795aadc86f286994ab6cc31873b83b2daebf7fa packages/cli/src/launcher.mjs +9b30a3bfe96a5d7c6956b6c75196c08bc35ceec302859337915ca1cfe2a76b76 packages/cli/tests/fixtures/launch-host.mjs +0a2a452fdb3a4ea68478e54b3ca6694c51d074fa29a0ad8ea3740eb1b44ab780 packages/cli/tests/host.test.mjs +1dbdb8166e8c47290bb4499b330ea32bec9a6f07179eef816edaa35603b1c408 packages/cli/tests/launcher.test.mjs +709bd331bb0d76f28b464e518f4e1fc3bb0b303614e79d7e82479dcd679043cc packages/cli/tests/verbs.test.mjs +570817222c5cc2f195963569e295db11f09f5dd56f19e3c9bdc220d5ee54435f packages/harness/README.md +34ef8212e27f052223443c66830839517f7a54ecb6ef7d85795b3e89c65b4d4c packages/harness/package.json +22efd0bed7991561920ad29f6bf9a1ab2d7384185fab4267684a037e026d8e85 packages/harness/src/bundle.mjs +32b5090760c95eea0e1232ff36ec13a1d9b58335b2eade621196dec1ebdbe784 packages/harness/src/claude-gate.mjs +be416a0c70cd84c2c53e3259296077c28c5d3006e0a151efc950b2ce73354604 packages/harness/src/gate.mjs +71e00113b8e5b55b410c7aae6232f76e972fc77aa68afa893da45c6b78d297e2 packages/harness/src/mcp-server.mjs +d19753fa72f0b57e751749359644093713bcb650bfac566f55d2bc05cb817121 packages/harness/src/pi-extension.mjs +1047aa092080e92efde2044dddaf82bacf428ed4b143c3846693471481f3612e packages/harness/src/runner.mjs +92153d9e2161ceef4ee76f2ff992014760a8c62b0ea709b51e2ea0ce965d3edb packages/harness/src/tools.mjs +96796238b7effab78cc6356ed8ea163f02aa39999d8dfc97fef83d45309d6574 packages/harness/tests/bundle.test.mjs +96524da43b212e84d78108cbbf05eef324c934f3ad9e0d4cb2edcb0f3df256d4 packages/harness/tests/claude-gate.test.mjs +197ec0f6c842552bea65fb2ab4c8cb8615fd6e691a2d0a592e1465d18a45d75f packages/harness/tests/claude-session.test.mjs +8392172b243356932c974bdca9b4c449a312ae7a042ee7a22e0f22fbb98dbec9 packages/harness/tests/fixtures/fake-adapter.mjs +38111dc604d32486941f9422654cc392fa2c7767e7f02d13ebbb0038b71669ce packages/harness/tests/gate.test.mjs +6e7509cfe6ad909440c25b750528360c0ba617c6c68b05d400bcd94b481c1f76 packages/harness/tests/helpers.mjs +793eb11f970e4a8eecddec4dc48c24331e4de795bc04bfadf806a2fc3a15a8b4 packages/harness/tests/mcp-server.test.mjs +c4798bb5681172e707d41446388e0490ca8a5398e801766988c8daba2646beaf packages/harness/tests/pi-session.test.mjs +c8f23144316501c3e163cf5a5566ddee552b3c93dee13594ae8c1eac66aedc64 packages/harness/tests/runner.test.mjs +ef9130a3cb1ca3e278a8ed370060afd1145d1ceb211a915e83d75c8346b04931 packages/harness/tests/tools.test.mjs +4e34456187387b02fa6d996c270dea4076b5d57952cddaa01f7a04843b351a02 packages/seat/README.md +382cbf7e0ff336911e288ce858bdbfbec693bc2b69879720d1f0b4c7d8455198 packages/seat/src/proc.mjs +5e181204de871d4f1098f5a63b5f80d87a44f5c01bf150101a6690bb1ccdca3d packages/seat/src/session.mjs +9a505d255c61034b3be738d359bc4a10acf08916c65675ee14dab2e29c060b04 packages/seat/tests/session.test.mjs +482ad6167fc96bf86928e0b467b3e173b174508fd913e297a8164d73f334d031 scripts/agent-host-dev.sh +b37d673aa5ce72c10b49018d8ffd9460f393eff7b638f1aa2065eecd608dde77 scripts/mosaic diff --git a/agents/darkwing/work/s6-review/r3/files.txt b/agents/darkwing/work/s6-review/r3/files.txt new file mode 100644 index 00000000..67685134 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/files.txt @@ -0,0 +1,42 @@ +adapters/README.md +adapters/claude/adapter.sh +adapters/pi/adapter.sh +docs/TOOLS.md +packages/bus/README.md +packages/bus/src/broker.mjs +packages/bus/src/process.mjs +packages/bus/src/runtime.mjs +packages/bus/tests/end-launch.test.mjs +packages/cli/README.md +packages/cli/src/cli.mjs +packages/cli/src/host.mjs +packages/cli/src/launcher.mjs +packages/cli/tests/fixtures/launch-host.mjs +packages/cli/tests/host.test.mjs +packages/cli/tests/launcher.test.mjs +packages/cli/tests/verbs.test.mjs +packages/harness/README.md +packages/harness/package.json +packages/harness/src/bundle.mjs +packages/harness/src/claude-gate.mjs +packages/harness/src/gate.mjs +packages/harness/src/mcp-server.mjs +packages/harness/src/pi-extension.mjs +packages/harness/src/runner.mjs +packages/harness/src/tools.mjs +packages/harness/tests/bundle.test.mjs +packages/harness/tests/claude-gate.test.mjs +packages/harness/tests/claude-session.test.mjs +packages/harness/tests/fixtures/fake-adapter.mjs +packages/harness/tests/gate.test.mjs +packages/harness/tests/helpers.mjs +packages/harness/tests/mcp-server.test.mjs +packages/harness/tests/pi-session.test.mjs +packages/harness/tests/runner.test.mjs +packages/harness/tests/tools.test.mjs +packages/seat/README.md +packages/seat/src/proc.mjs +packages/seat/src/session.mjs +packages/seat/tests/session.test.mjs +scripts/agent-host-dev.sh +scripts/mosaic diff --git a/agents/darkwing/work/s6-review/r3/gate.sh b/agents/darkwing/work/s6-review/r3/gate.sh new file mode 100755 index 00000000..10f52da8 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/gate.sh @@ -0,0 +1,14 @@ +#!/bin/bash +export TMPDIR=~/darkwing-scratch/tmp DOCKER_HOST=unix:///nonexistent.sock +cd ~/darkwing-scratch/r41c/wt +O=~/darkwing-scratch/r41c/out +: > $O/summary.txt +for p in harness seat cli bus business; do + node --test "packages/$p/tests/*.test.mjs" > $O/node-$p.txt 2>&1; e=$? + echo "node-$p exit=$e $(grep -E '^ℹ (pass|fail)' $O/node-$p.txt | tr '\n' ' ')" >> $O/summary.txt +done +for s in test-auth test-config test-conductor test-queue test-foundation test-extension-package test-release test-discord test-task; do + scripts/$s.sh > $O/$s.txt 2>&1; e=$? + echo "$s exit=$e $(grep -E 'passed, [0-9]+ failed' $O/$s.txt | tail -1)" >> $O/summary.txt +done +echo DONE >> $O/summary.txt diff --git a/agents/darkwing/work/s6-review/r3/interdiff.patch b/agents/darkwing/work/s6-review/r3/interdiff.patch new file mode 100644 index 00000000..e200eaaa --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/interdiff.patch @@ -0,0 +1,318 @@ +--- r2wt/packages/cli/tests/host.test.mjs 2026-10-09 22:01:00.286672229 -0500 ++++ wt/packages/cli/tests/host.test.mjs 2026-10-09 22:00:50.898562239 -0500 +@@ -262,6 +262,29 @@ + } + }); + ++test("a broker reply with no request waiting breaks the channel and the host exits 1", { timeout: 30000 }, async (t) => { ++ // A request sent to the real broker past the host's queue: its reply ++ // arrives when nothing is waiting (Darkwing round 2, Mr). ++ const children = []; ++ const onChild = ({ process: child }) => children.push(child); ++ subscribe("child_process", onChild); ++ t.after(() => unsubscribe("child_process", onChild)); ++ const root = tmp(t); ++ const f = fixture(root); ++ makeDeployment(root); ++ const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); ++ const logs = []; ++ const host = await startHost({ boot, business: "acme", log: (l) => logs.push(l) }); ++ t.after(() => host.close(0)); ++ const record = { business: "acme", role: "coder", run: "coder-run", harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) }; ++ const coder = await host.bindLaunch(record); ++ children.find((c) => c.pid === host.pids.broker).send({ op: "identity", cap: coder.cap, id: 1_000_000 }); ++ const late = new Promise((r) => setTimeout(r, 5000, "still running").unref()); ++ assert.equal(await Promise.race([host.done, late]), 1); ++ assert.ok(logs.includes("broker channel broken (reply with no request waiting); stopping the host"), logs.join("\n")); ++ await assert.rejects(host.op({ op: "identity", cap: coder.cap }), (e) => e.code === "broker-channel-broken"); ++}); ++ + test("a notifier that refuses stops the broker and the host refuses with exit 3", async (t) => { + const root = tmp(t); + const f = fixture(root); +--- r2wt/packages/cli/tests/launcher.test.mjs 2026-10-09 22:01:00.286672229 -0500 ++++ wt/packages/cli/tests/launcher.test.mjs 2026-10-09 22:00:50.898562239 -0500 +@@ -103,7 +103,7 @@ + + const log = (f) => readFileSync(launchLogFile(f.dataRoot, "acme"), "utf8").trim().split("\n").map((l) => JSON.parse(l)); + +-test("bus start --pm: the PM runs under its own PID namespace, registered, with a manifest", { skip }, async (t) => { ++test("bus start --pm: the PM runs under its own PID namespace, registered, with a manifest", { skip, timeout: 30000 }, async (t) => { + const { f, launcher, host, cto, close } = await setup(t); + const sock = launchSocketPath(f.dataRoot); + assert.equal(statSync(sock).mode & 0o777, 0o600); +@@ -173,7 +173,7 @@ + assert.equal(end.exitCode, 0); + }); + +-test("the PM launches a coder through its launch tool; the coder answers; refusals name their code", { skip }, async (t) => { ++test("the PM launches a coder through its launch tool; the coder answers; refusals name their code", { skip, timeout: 30000 }, async (t) => { + const { f, launcher, client, cto } = await setup(t); + const sock = launchSocketPath(f.dataRoot); + const pm = await launcher.launchPm(); +@@ -234,7 +234,7 @@ + assert.ok(readSessions(f.dataRoot).some((s) => s.run === pm.run)); + }); + +-test("a runner that stops at once ends its launch with the runner's reason", { skip }, async (t) => { ++test("a runner that stops at once ends its launch with the runner's reason", { skip, timeout: 30000 }, async (t) => { + // pm's tracker token has expired: the runner's founder-credential stop + // exits 20 before role.claim, and the host records founder-credentials. + const { f, launcher } = await setup(t, (doc) => { +@@ -250,7 +250,7 @@ + }); + + for (const exited of [false, true]) { +- test(`a host that died hard leaves its sessions to the next host, which ${exited ? "finds one already exited (23)" : "kills them"} and ends their runs so the role can be launched again`, { skip }, async (t) => { ++ test(`a host that died hard leaves its sessions to the next host, which ${exited ? "finds one already exited (23)" : "kills them"} and ends their runs so the role can be launched again`, { skip, timeout: 30000 }, async (t) => { + const given = prepare(t); + const { f } = given; + const old = spawn(process.execPath, [HOST, given.adapter], { env: f.env, stdio: ["ignore", "pipe", "inherit"] }); +@@ -311,7 +311,7 @@ + }); + } + +-test("a malformed sessions.json refuses the host start with exit 3 and stays as it was", async (t) => { ++test("a malformed sessions.json refuses the host start with exit 3 and stays as it was", { timeout: 30000 }, async (t) => { + const given = prepare(t); + const file = sessionsFile(given.f.dataRoot); + mkdirSync(join(given.f.dataRoot, "bus-host"), { mode: 0o700 }); +@@ -323,7 +323,7 @@ + + const within = (p, ms, what) => Promise.race([p, new Promise((_, reject) => setTimeout(() => reject(new Error(`${what} took over ${ms} ms`)), ms).unref())]); + +-test("an over-long launch request is refused at once, not at the 10 s idle timeout", async (t) => { ++test("an over-long launch request is refused at once, not at the 10 s idle timeout", { timeout: 30000 }, async (t) => { + const { f } = await setup(t); + const s = connect(launchSocketPath(f.dataRoot)); + t.after(() => s.destroy()); +--- r2wt/packages/harness/README.md 2026-10-09 22:01:00.286672229 -0500 ++++ wt/packages/harness/README.md 2026-10-09 22:00:50.898562239 -0500 +@@ -83,6 +83,27 @@ + directories first. Pi calls it from the extension, Claude Code from + `claude-gate.mjs`. + ++Pi's `read` doesn't always open the name it is given. When that name ++doesn't exist, it tries other spellings of the whole resolved path: a ++narrow no-break space (U+202F) before ` AM.` or ` PM.`, the NFD form, a ++curly apostrophe (U+2019) for `'`, and NFD with the curly apostrophe. So ++for `read`, the gate also checks every one of those spellings that exists ++as a name, built from both the workspace as given and its real path (Pi's ++working directory). If any of them resolves outside the workspace or goes ++through a dangling symlink, the read is refused, whichever one Pi would ++pick. A spelling that doesn't exist is ignored. Claude Code's `Read` gets ++the same check, though in Darkwing's round 2 probe Claude Code's own ++symlink check already stopped the one variant it tries (AM/PM). `write`, ++`edit`, `grep`, `find` and `ls` take the name as given in Pi 0.85.1, so ++they get no such check. ++ ++The gate copies Pi 0.85.1 code, so recheck these files on a Pi upgrade: ++`dist/utils/paths.js` (`normalizePath`), `dist/core/tools/path-utils.js` ++(`resolveToCwd`, `resolveReadPathAsync`) and which tools in ++`dist/core/tools/` call `resolveReadPath*`. The `pi` binary runs the ++bundled copy under `dist/bundle/`; in 0.85.1 it holds the same code, and ++only `read` and the CLI's own file arguments call `resolveReadPath*`. ++ + ## The runner + + `node runner.mjs `, with `{"cap": "..."}` and a newline on stdin. +@@ -156,6 +177,11 @@ + manifest but not applied to either harness. + - **`--restricted`** (Claude Code) is not one of the S0 lines. It is what + keeps `CLAUDE.md` files and auto-memory out of the prompt (above). ++- **Other spellings.** A read is refused when a spelling Pi might open ++ points outside the workspace, even if the name as given exists and is ++ inside, and Pi would open that one. The same holds when a directory ++ next to the workspace has another spelling of its path, for example ++ `jo’s/ws` beside a workspace in `jo's/ws`. + - **The gate checks a path when the call is made.** A link created or + changed between the check and the tool's own open (by `bash`, or by + another process of the same user) isn't seen. That is the same reach as +--- r2wt/packages/harness/src/gate.mjs 2026-10-09 22:01:00.287853049 -0500 ++++ wt/packages/harness/src/gate.mjs 2026-10-09 22:00:50.899987075 -0500 +@@ -62,13 +62,60 @@ + } + } + +-export function insideWorkspace(workspace, p) { ++function within(workspace, path) { + const root = realpathSync(workspace); +- const s = normalise(p); +- const target = real(isAbsolute(s) ? resolve(s) : resolve(workspace, s)); ++ const target = real(path); + return target === root || target.startsWith(root + sep); + } + ++export function insideWorkspace(workspace, p) { ++ const s = normalise(p); ++ return within(workspace, isAbsolute(s) ? resolve(s) : resolve(workspace, s)); ++} ++ ++// Pi's read (dist/core/tools/path-utils.js resolveReadPathAsync) opens ++// another spelling when the resolved path doesn't exist: U+202F before ++// AM/PM, then NFD, then U+2019 for ', then both. Each applies to the whole ++// resolved path, directory names included, and Pi resolves against its cwd, ++// the workspace's real path. So every spelling that exists as a name is ++// checked, not only the one Pi would pick, and the check doesn't depend on ++// which of them exists when Pi opens it. Claude Code's Read gets the same ++// check; its own retries aren't documented. ++function spellings(workspace, p) { ++ const s = normalise(p); ++ const bases = isAbsolute(s) ? [resolve(s)] : [resolve(workspace, s), resolve(realpathSync(workspace), s)]; ++ const curly = (v) => v.replace(/'/g, "\u2019"); ++ const out = new Set(); ++ for (const r of bases) { ++ const nfd = r.normalize("NFD"); ++ for (const v of [r.replace(/ (AM|PM)\./gi, "\u202F$1."), nfd, curly(r), curly(nfd)]) if (v !== r) out.add(v); ++ } ++ return out; ++} ++ ++// The first spelling that exists and resolves outside, or through a ++// dangling link, as a refusal reason; null if there is none. ++function otherSpelling(workspace, tool, p) { ++ for (const v of spellings(workspace, p)) { ++ let found; ++ try { ++ found = lstatSync(v, { throwIfNoEntry: false }); ++ } catch (error) { ++ // A file used as a directory: Pi's access() fails too. ++ if (error.code === "ENOTDIR") continue; ++ return `${tool} path can't be checked under another spelling: ${error.code ?? error.message}`; ++ } ++ if (!found) continue; ++ try { ++ if (!within(workspace, v)) return `${tool} path is outside the workspace under another spelling: ${p} -> ${JSON.stringify(v)}`; ++ } catch (error) { ++ if (error.code === "dangling-symlink") return `${tool} path goes through a dangling symlink under another spelling: ${p} -> ${JSON.stringify(v)}`; ++ return `${tool} path can't be checked under another spelling: ${error.code ?? error.message}`; ++ } ++ } ++ return null; ++} ++ + // decide(policy, tool, input) -> { allow: true } | { allow: false, reason } + // policy { harness: "pi" | "claude-code", workspace, tools: [pi names], typed: [typed tool names] } + export function decide(policy, tool, input) { +@@ -98,5 +145,14 @@ + if (error.code === "dangling-symlink") return no(`${tool} path goes through a dangling symlink: ${value}`); + return no(`${tool} path can't be checked: ${error.code ?? error.message}`); + } ++ if (tool === (claude ? "Read" : "read")) { ++ let other; ++ try { ++ other = otherSpelling(policy.workspace, tool, value); ++ } catch (error) { ++ return no(`${tool} path can't be checked: ${error.code ?? error.message}`); ++ } ++ if (other) return no(other); ++ } + return { allow: true }; + } +--- r2wt/packages/harness/tests/gate.test.mjs 2026-10-09 22:01:00.288274261 -0500 ++++ wt/packages/harness/tests/gate.test.mjs 2026-10-09 22:00:50.900285193 -0500 +@@ -102,6 +102,62 @@ + } + }); + ++// Pi's read opens another spelling when the name it's given doesn't exist ++// (path-utils.js resolveReadPathAsync). Asked name -> name on disk. ++const SPELLINGS = [ ++ ["quote", "notes's.txt", "notes\u2019s.txt"], ++ ["ampm", "shot 9.41 AM.png", "shot 9.41\u202FAM.png"], ++ ["nfd", "r\u00E9sum\u00E9.txt", "r\u00E9sum\u00E9.txt".normalize("NFD")], ++ ["nfd and quote", "d'\u00E9cran.png", "d\u2019\u00E9cran.png".normalize("NFD")], ++ // Each family alone, on a name with both an apostrophe and an accent. ++ ["nfd, straight quote", "l'\u00E9t\u00E9.txt", "l'\u00E9t\u00E9.txt".normalize("NFD")], ++ ["quote, composed", "l'\u00E9t\u00E9.md", "l\u2019\u00E9t\u00E9.md"], ++]; ++ ++test("read is checked under every spelling pi's read would open, in both harnesses", (t) => { ++ for (const [harness, read, write, field] of [["pi", "read", "write", "path"], ["claude-code", "Read", "Write", "file_path"]]) { ++ for (const [, asked, disk] of SPELLINGS) { ++ const { dir, workspace, policy } = setup(t, harness, ["read", "write"]); ++ mkdirSync(join(dir, "outside")); ++ writeFileSync(join(dir, "outside", "secret.txt"), "s"); ++ symlinkSync(join(dir, "outside", "secret.txt"), join(workspace, disk)); ++ const why = new RegExp(`outside the workspace under another spelling: (.*/)?${asked.replace(/[.']/g, "\\$&")}`); ++ blocked(decide(policy, read, { [field]: asked }), why); ++ blocked(decide(policy, read, { [field]: join(workspace, asked) }), why); ++ // Write takes the name as given, so writing the asked name stays inside. ++ allowed(decide(policy, write, { [field]: asked })); ++ // The same spelling pointing inside is fine. ++ const inner = setup(t, harness, ["read"]); ++ symlinkSync(join(inner.workspace, "a.txt"), join(inner.workspace, disk)); ++ allowed(decide(inner.policy, read, { [field]: asked })); ++ } ++ } ++}); ++ ++test("other spellings cover directories, dangling links and pi's cwd", (t) => { ++ const { dir, workspace, policy } = setup(t, "pi", ["read"]); ++ mkdirSync(join(dir, "outside")); ++ writeFileSync(join(dir, "outside", "s.txt"), "s"); ++ // A directory name is respelled too. ++ symlinkSync(join(dir, "outside"), join(workspace, "it\u2019s")); ++ blocked(decide(policy, "read", { path: "it's/s.txt" }), /outside the workspace under another spelling/); ++ // A dangling link under another spelling is refused, like the name itself. ++ symlinkSync(join(dir, "nowhere"), join(workspace, "gone\u2019s")); ++ blocked(decide(policy, "read", { path: "gone's" }), /dangling symlink under another spelling/); ++ // A spelling that doesn't exist, or uses a file as a directory, isn't opened. ++ allowed(decide(policy, "read", { path: "nobody's.txt" })); ++ writeFileSync(join(workspace, "f\u2019s"), "f"); ++ allowed(decide(policy, "read", { path: "f's/x" })); ++ // Pi resolves against its cwd, the workspace's real path, and respells ++ // that too: here the real path has an apostrophe the given path lacks. ++ mkdirSync(join(dir, "jo's", "ws"), { recursive: true }); ++ mkdirSync(join(dir, "jo\u2019s", "ws"), { recursive: true }); ++ writeFileSync(join(dir, "jo\u2019s", "ws", "x.txt"), "x"); ++ symlinkSync(join(dir, "jo's"), join(dir, "jo")); ++ const viaLink = { ...policy, workspace: join(dir, "jo", "ws") }; ++ blocked(decide(viaLink, "read", { path: "x.txt" }), /outside the workspace under another spelling/); ++}); ++ + test("claude path fields per tool", (t) => { + const { workspace, policy } = setup(t, "claude-code", ["read", "write", "edit", "grep", "find"]); + allowed(decide(policy, "Read", { file_path: join(workspace, "a.txt") })); +--- r2wt/packages/harness/tests/pi-session.test.mjs 2026-10-09 22:01:00.288274261 -0500 ++++ wt/packages/harness/tests/pi-session.test.mjs 2026-10-09 22:00:50.900285193 -0500 +@@ -120,6 +120,39 @@ + assert.ok(existsSync(s.turnMarker)); + }); + ++test("pi: a read is refused when pi would open another spelling outside", async (t) => { ++ // Asked name -> link on disk; pi's read opens the link when the asked ++ // name doesn't exist (path-utils.js resolveReadPathAsync). ++ const spellings = [ ++ ["notes's.txt", "notes\u2019s.txt"], ++ ["shot 9.41 AM.png", "shot 9.41\u202FAM.png"], ++ ["r\u00E9sum\u00E9.txt", "r\u00E9sum\u00E9.txt".normalize("NFD")], ++ ["d'\u00E9cran.png", "d\u2019\u00E9cran.png".normalize("NFD")], ++ ["l'\u00E9t\u00E9.txt", "l'\u00E9t\u00E9.txt".normalize("NFD")], ++ ["l'\u00E9t\u00E9.md", "l\u2019\u00E9t\u00E9.md"], ++ ]; ++ const { dir, workspace, s, env } = await session(t, [ ++ ...spellings.map(([asked]) => ({ name: "read", input: { path: asked } })), ++ { name: "read", input: { path: "inside's.txt" } }, ++ ]); ++ mkdirSync(join(dir, "outside")); ++ writeFileSync(join(dir, "outside", "secret.txt"), "SECRET-OUTSIDE\n"); ++ for (const [, disk] of spellings) symlinkSync(join(dir, "outside", "secret.txt"), join(workspace, disk)); ++ // The same respelling pointing inside is still read. ++ symlinkSync(join(workspace, "notes.txt"), join(workspace, "inside\u2019s.txt")); ++ const r = await turn(env, "Message 1 from jason, class REQUEST:\n\nread the files", workspace); ++ assert.equal(r.code, 0, r.stderr); ++ assert.doesNotMatch(r.stdout, /SECRET/); ++ const results = JSON.parse(r.stdout.trim().slice("ANSWER ".length)); ++ assert.equal(results.length, spellings.length + 1); ++ spellings.forEach(([asked], i) => { ++ assert.equal(results[i][0], true, asked); ++ assert.match(results[i][1], /outside the workspace under another spelling/, asked); ++ }); ++ assert.deepEqual(results.at(-1), [false, "inside\n"]); ++ assert.ok(existsSync(s.turnMarker)); ++}); ++ + test("pi: a missing extension refuses before any model call", async (t) => { + const { dir, api, s, env } = await session(t, []); + const r = await turn({ ...env, MOSAIC_EXTENSIONS: join(dir, "missing.mjs") }, "x", s.workspace); diff --git a/agents/darkwing/work/s6-review/r3/mut/MA-pi-only-harness.txt b/agents/darkwing/work/s6-review/r3/mut/MA-pi-only-harness.txt new file mode 100644 index 00000000..42fa7c3c --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/MA-pi-only-harness.txt @@ -0,0 +1,85 @@ +✔ sessionModel: agent vars win, then the system's execution settings (9.358532ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.398285ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.527849ms) +✔ a bundle is written once: an existing file refuses (2.071583ms) +✔ a path with a single quote can't go into the hook command (1.761433ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (123.554651ms) +✔ a missing or wrong policy, or a bad event, exits 2 (82.057364ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1089.837021ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (737.701356ms) +✔ claude: the hook alone blocks a path outside the workspace (465.176471ms) +✔ claude: a second turn resumes the first turn's session (719.164258ms) +✔ claude adapter: --restricted is always passed (5.526811ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (749.125161ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.493927ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (2.894317ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.846601ms) +✔ file tool paths must resolve inside the workspace (0.993882ms) +✔ pi's own path normalisation can't be used to step out (0.8916ms) +✔ a symlink inside the workspace that points out is outside (0.801529ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.788934ms) +✖ read is checked under every spelling pi's read would open, in both harnesses (9.402305ms) +✔ other spellings cover directories, dangling links and pi's cwd (2.380602ms) +✔ claude path fields per tool (0.956169ms) +✔ glob patterns stay inside the workspace (1.035235ms) +✔ a path that can't be checked is blocked (0.530203ms) +✔ initialize, ping and tools/list (39.441298ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (32.39596ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (33.457902ms) +✔ a missing argument is a usage error (33.942319ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (356.826829ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (324.927588ms) +✔ pi: a read is refused when pi would open another spelling outside (344.945871ms) +✔ pi: a missing extension refuses before any model call (6.876995ms) +✔ pi: an extension without its configuration fails pi's start (265.829784ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.210381ms) +✔ turnRequest names the sender, class, reply and decision (0.173436ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (254.240602ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (96.44878ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (392.016492ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1309.272562ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (165.612545ms) +✔ founder credentials stop before the claim (20) (165.730608ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (191.212879ms) +✔ the launch ending under a running session exits 22 (149.35259ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (244.991401ms) +✔ a broker that is down at the claim exits 23, not 21 (112.331502ms) +✔ no capability, or a malformed one, on stdin exits 2 (189.681234ms) +✔ a missing or malformed policy exits 2 before the claim (127.377256ms) +✔ the PM gets launch, its task verbs and the reads (6.818221ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.149355ms) +✔ launch only when the business's launch block names the instance as launcher (1.624567ms) +✔ an action outside the instance's authority has no tool (1.44195ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (9.277411ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 52 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10334.028949 + +✖ failing tests: + +test at packages/harness/tests/gate.test.mjs:117:1 +✖ read is checked under every spelling pi's read would open, in both harnesses (9.402305ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at blocked (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:19:10) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:125:7) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/MB-no-real-base-harness.txt b/agents/darkwing/work/s6-review/r3/mut/MB-no-real-base-harness.txt new file mode 100644 index 00000000..d0662a99 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/MB-no-real-base-harness.txt @@ -0,0 +1,85 @@ +✔ sessionModel: agent vars win, then the system's execution settings (8.566028ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.074535ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.408252ms) +✔ a bundle is written once: an existing file refuses (2.772069ms) +✔ a path with a single quote can't go into the hook command (1.564063ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (117.170068ms) +✔ a missing or wrong policy, or a bad event, exits 2 (76.439081ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1092.382926ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (723.07052ms) +✔ claude: the hook alone blocks a path outside the workspace (437.790038ms) +✔ claude: a second turn resumes the first turn's session (706.64768ms) +✔ claude adapter: --restricted is always passed (5.065955ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (735.740968ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.422196ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (3.175453ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.916415ms) +✔ file tool paths must resolve inside the workspace (1.439155ms) +✔ pi's own path normalisation can't be used to step out (1.34261ms) +✔ a symlink inside the workspace that points out is outside (1.126732ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.825163ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (15.322363ms) +✖ other spellings cover directories, dangling links and pi's cwd (2.385802ms) +✔ claude path fields per tool (1.137465ms) +✔ glob patterns stay inside the workspace (0.670222ms) +✔ a path that can't be checked is blocked (0.36089ms) +✔ initialize, ping and tools/list (39.621731ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (31.219015ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (30.074064ms) +✔ a missing argument is a usage error (26.975524ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (342.776996ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (319.23647ms) +✔ pi: a read is refused when pi would open another spelling outside (307.599481ms) +✔ pi: a missing extension refuses before any model call (6.637971ms) +✔ pi: an extension without its configuration fails pi's start (249.230312ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.184883ms) +✔ turnRequest names the sender, class, reply and decision (0.253545ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (240.675801ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (97.530314ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (366.718888ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1310.292612ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (156.386139ms) +✔ founder credentials stop before the claim (20) (178.388986ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (195.113915ms) +✔ the launch ending under a running session exits 22 (150.862288ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (236.983901ms) +✔ a broker that is down at the claim exits 23, not 21 (90.300934ms) +✔ no capability, or a malformed one, on stdin exits 2 (180.286601ms) +✔ a missing or malformed policy exits 2 before the claim (132.858844ms) +✔ the PM gets launch, its task verbs and the reads (6.987897ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.444742ms) +✔ launch only when the business's launch block names the instance as launcher (2.043716ms) +✔ an action outside the instance's authority has no tool (2.093997ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (11.192947ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 52 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10263.047304 + +✖ failing tests: + +test at packages/harness/tests/gate.test.mjs:137:1 +✖ other spellings cover directories, dangling links and pi's cwd (2.385802ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at blocked (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:19:10) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:158:3) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/MC-no-nfd-curly-harness.txt b/agents/darkwing/work/s6-review/r3/mut/MC-no-nfd-curly-harness.txt new file mode 100644 index 00000000..8a74188a --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/MC-no-nfd-curly-harness.txt @@ -0,0 +1,103 @@ +✔ sessionModel: agent vars win, then the system's execution settings (10.423796ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (3.914344ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.407099ms) +✔ a bundle is written once: an existing file refuses (2.032253ms) +✔ a path with a single quote can't go into the hook command (2.335414ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (116.424322ms) +✔ a missing or wrong policy, or a bad event, exits 2 (82.940953ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1091.478948ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (743.685519ms) +✔ claude: the hook alone blocks a path outside the workspace (431.911335ms) +✔ claude: a second turn resumes the first turn's session (706.06674ms) +✔ claude adapter: --restricted is always passed (5.068282ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (755.529214ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.982961ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.11275ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.956898ms) +✔ file tool paths must resolve inside the workspace (1.06297ms) +✔ pi's own path normalisation can't be used to step out (0.906404ms) +✔ a symlink inside the workspace that points out is outside (0.943109ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.798636ms) +✖ read is checked under every spelling pi's read would open, in both harnesses (7.371341ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.833526ms) +✔ claude path fields per tool (0.876754ms) +✔ glob patterns stay inside the workspace (0.975302ms) +✔ a path that can't be checked is blocked (0.528582ms) +✔ initialize, ping and tools/list (46.020346ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (31.372102ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (38.784287ms) +✔ a missing argument is a usage error (30.636398ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (346.29026ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (338.979029ms) +✖ pi: a read is refused when pi would open another spelling outside (308.833029ms) +✔ pi: a missing extension refuses before any model call (6.965714ms) +✔ pi: an extension without its configuration fails pi's start (250.888668ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.233953ms) +✔ turnRequest names the sender, class, reply and decision (0.183418ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (257.369793ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (101.543834ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (413.089843ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1287.433399ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (152.333233ms) +✔ founder credentials stop before the claim (20) (178.76855ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (199.319786ms) +✔ the launch ending under a running session exits 22 (141.706732ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (252.747209ms) +✔ a broker that is down at the claim exits 23, not 21 (91.79929ms) +✔ no capability, or a malformed one, on stdin exits 2 (188.941134ms) +✔ a missing or malformed policy exits 2 before the claim (142.45418ms) +✔ the PM gets launch, its task verbs and the reads (8.970935ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.816368ms) +✔ launch only when the business's launch block names the instance as launcher (2.300242ms) +✔ an action outside the instance's authority has no tool (2.202629ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (8.591287ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 51 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10317.199481 + +✖ failing tests: + +test at packages/harness/tests/gate.test.mjs:117:1 +✖ read is checked under every spelling pi's read would open, in both harnesses (7.371341ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at blocked (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:19:10) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:125:7) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } + +test at packages/harness/tests/pi-session.test.mjs:123:1 +✖ pi: a read is refused when pi would open another spelling outside (308.833029ms) + AssertionError [ERR_ASSERTION]: The input was expected to not match the regular expression /SECRET/. Input: + + `ANSWER [[true,"mosaic gate: read path is outside the workspace under another spelling: notes's."],[true,"mosaic gate: read path is outside the workspace under another spelling: shot 9.4"],[true,"mosaic gate: read path is outside the workspace under another spelling: résumé.t"],[false,"SECRET-OUTSIDE\\n"],[true,"mosaic gate: read path is outside the workspace under another spelling: l'été.tx"],[true,"mosaic gate: read path is outside the workspace under another spelling: l'été.md"],[false,"inside\\n"]]\n` + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/pi-session.test.mjs:145: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: `ANSWER [[true,"mosaic gate: read path is outside the workspace under another spelling: notes's."],[true,"mosaic gate: read path is outside the workspace under another spelling: shot 9.4"],[true,"mosaic gate: read path is outside the workspace under another spelling: résumé.t"],[false,"SECRET-OUTSIDE\\n"],[true,"mosaic gate: read path is outside the workspace under another spelling: l'été.tx"],[true,"mosaic gate: read path is outside the workspace under another spelling: l'été.md"],[false,"inside\\n"]]\n`, + expected: /SECRET/, + operator: 'doesNotMatch', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-cli.txt new file mode 100644 index 00000000..2bf2ff6a --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-cli.txt @@ -0,0 +1,115 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (125.098541ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (134.665935ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (96.766909ms) +✔ decide prints a declining choice as declining (108.478466ms) +✔ an unknown outcome is reported once and never resent (83.652622ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (85.260087ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (85.215231ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (47.360022ms) +✔ every human command refuses inside an agent run before it touches the bus (74.792694ms) +✔ usage errors exit 4; no business and no host is a usage error (67.921609ms) +✔ agents and tasks print through the broker (75.297097ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.397368ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (42.16644ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (32.376277ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (32.360128ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (28.459949ms) +✔ a business without tracker.baseUrl gets no trackers entry (27.435579ms) +✔ an unknown business and a broken system config refuse with exit 3 (52.425052ms) +✔ empty views say so (1.054835ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.31622ms) +✔ tasks print the tracker fields the snapshot carries (0.230229ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (900.777403ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (197.314943ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (106.753089ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1123.524195ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (234.234287ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (113.460887ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (156.637981ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (147.392036ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (113.446985ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (147.381832ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (94.058726ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (159.970362ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.968625ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.36153ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (228.950236ms) +✔ bus start refuses with exit 3 without a notifier config (97.197829ms) +✔ bus start runs until bus stop; status reports it while it runs (656.505643ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.52731ms) +✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (30264.958209ms) +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (18485.409173ms) +✔ a runner that stops at once ends its launch with the runner's reason (194.401086ms) +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (30544.742537ms) +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (33061.504326ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (105.073482ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (108.473075ms) +✔ a launch client that never closes its side doesn't hold the host's close (122.229839ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1224.323487ms) +✔ zoned uses the IANA zone across DST (15.998202ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (106.49839ms) +✔ two blocking decisions get two DMs with different nonces (136.681083ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.247401ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (105.173087ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (91.885581ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (88.046523ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (96.766445ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (133.166052ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (121.365828ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (96.241289ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (68.046994ms) +✔ an inbox read failure is logged and the next poll retries (0.652732ms) +✔ no Discord id reaches the journal or the log (52.636044ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (15.640962ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (15.244681ms) +✔ the journal: a whole file that is one torn line truncates to empty (10.205265ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.5312ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.565166ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.778854ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.310747ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.451018ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.375168ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.390645ms) +✔ digest content stays within Discord's 2000 characters (0.230013ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.867714ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (378.858886ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (38.33439ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2142.861729ms) +✔ busExit and refuseInsideAgent (0.427438ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (208.921317ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1129.911757ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (45.073004ms) +✔ launches off and on go to the broker and change the business's launch state (54.562449ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (55.056715ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 79 +ℹ fail 1 +ℹ cancelled 3 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 114201.684561 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:106:1 +✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (30264.958209ms) + 'test timed out after 30000ms' + +test at packages/cli/tests/launcher.test.mjs:176:1 +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (18485.409173ms) + Error: timed out waiting + at until (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:43:9) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:216:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (30544.742537ms) + 'test timed out after 30000ms' + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (33061.504326ms) + 'test timed out after 30000ms' diff --git a/agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-harness.txt new file mode 100644 index 00000000..aa15e37a --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Ma-late-signals-harness.txt @@ -0,0 +1,124 @@ +✔ sessionModel: agent vars win, then the system's execution settings (11.140494ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.048456ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.780692ms) +✔ a bundle is written once: an existing file refuses (2.191088ms) +✔ a path with a single quote can't go into the hook command (2.698237ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (123.019613ms) +✔ a missing or wrong policy, or a bad event, exits 2 (81.316548ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1102.998811ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (745.482939ms) +✔ claude: the hook alone blocks a path outside the workspace (438.308218ms) +✔ claude: a second turn resumes the first turn's session (723.668481ms) +✔ claude adapter: --restricted is always passed (5.009441ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (745.660141ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.497673ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (2.811459ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.694063ms) +✔ file tool paths must resolve inside the workspace (0.964219ms) +✔ pi's own path normalisation can't be used to step out (0.928349ms) +✔ a symlink inside the workspace that points out is outside (0.820113ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.912616ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (13.371484ms) +✔ other spellings cover directories, dangling links and pi's cwd (2.084494ms) +✔ claude path fields per tool (0.760987ms) +✔ glob patterns stay inside the workspace (0.711647ms) +✔ a path that can't be checked is blocked (0.351555ms) +✔ initialize, ping and tools/list (45.796686ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (32.037678ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (30.356779ms) +✔ a missing argument is a usage error (26.986749ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (361.398704ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (318.044545ms) +✔ pi: a read is refused when pi would open another spelling outside (319.989916ms) +✔ pi: a missing extension refuses before any model call (6.645305ms) +✔ pi: an extension without its configuration fails pi's start (254.355419ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.161435ms) +✔ turnRequest names the sender, class, reply and decision (0.327442ms) +✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (247.588857ms) +✖ a SIGTERM before the claim stops the runner with exit 0 and no claim (109.591138ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (392.919759ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1294.268687ms) +✖ SIGTERM during a turn kills the turn's process group and still exits 0 (145.929592ms) +✔ founder credentials stop before the claim (20) (181.0226ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (195.806934ms) +✔ the launch ending under a running session exits 22 (143.244036ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (243.51654ms) +✔ a broker that is down at the claim exits 23, not 21 (95.036286ms) +✔ no capability, or a malformed one, on stdin exits 2 (190.597233ms) +✔ a missing or malformed policy exits 2 before the claim (125.336817ms) +✔ the PM gets launch, its task verbs and the reads (6.275456ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.228187ms) +✔ launch only when the business's launch block names the instance as launcher (1.727758ms) +✔ an action outside the instance's authority has no tool (1.644912ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (7.802134ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 50 +ℹ fail 3 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10310.317054 + +✖ failing tests: + +test at packages/harness/tests/runner.test.mjs:148:1 +✖ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (247.588857ms) + AssertionError [ERR_ASSERTION]: runner: demo/coder claimed by run coder-run + + + null !== 0 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:167: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: null, + expected: 0, + operator: 'strictEqual', + diff: 'simple' + } + +test at packages/harness/tests/runner.test.mjs:173:1 +✖ a SIGTERM before the claim stops the runner with exit 0 and no claim (109.591138ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + - 0, + null, + + 'SIGTERM' + ] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:192: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: [ null, 'SIGTERM' ], + expected: [ 0, null ], + operator: 'deepStrictEqual', + diff: 'simple' + } + +test at packages/harness/tests/runner.test.mjs:239:1 +✖ SIGTERM during a turn kills the turn's process group and still exits 0 (145.929592ms) + AssertionError [ERR_ASSERTION]: runner: demo/coder claimed by run coder-run + + + null !== 0 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:249: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: null, + expected: 0, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-bus.txt b/agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-bus.txt new file mode 100644 index 00000000..e8ac1c3b --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-bus.txt @@ -0,0 +1,102 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (141.752229ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (247.105736ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (232.074913ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (163.437494ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (134.182686ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (128.492959ms) +✔ task action subjects and linked decision trail are complete and ordered (141.350634ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (87.271074ms) +✔ business isolation includes inherited object names and cross-business message references (154.949676ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (205.599102ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (105.988045ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (155.526684ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (103.210466ms) +✔ both arbiters require human resolution when their cross-role route is themselves (168.486214ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.685403ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.552371ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.259922ms) +✔ expiry refuses use and env references never become client data (1.611144ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.567589ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.66433ms) +✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (111.925286ms) +✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (114.03898ms) +✖ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (143.641995ms) +✔ endLaunch leaves a claim another run took alone (147.819902ms) +✔ refuse records action.refused against the caller with the code only (115.497532ms) +✔ launches off refuses role.launch with launch-revoked until launches on (167.568365ms) +✔ broker process: launch ops authorize role.launch, record refusals and end runs (216.389965ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (3.390024ms) +✔ process reader gets own kernel identity without exposing environment values (0.921905ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.105945ms) +✔ real pid 1 remains inspectable when its environment is protected (0.454306ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (159.790733ms) +✔ missing and foreign-business citations refuse and roll back message and grant (173.665975ms) +✔ cross-role sends still need a matching resolved decision and consume it once (219.510088ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (169.453786ms) +✔ startup token refusal returns safe code without value or partial listening broker (37.810006ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (171.481213ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (165.155134ms) +✔ trusted host registers later launches; socket clients never have a registration verb (162.948817ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (36.060833ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (136.155747ms) +✔ v3b prototype refusals, views and append-only mutations (931.964108ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (222.420689ms) +✔ another run cannot consume an approval; a failed check leaves it usable (208.008517ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (136.335702ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (279.703548ms) +✔ class drift gated to cross-role refuses before consumption (171.91116ms) +✔ class drift cross-role to gated refuses before consumption (174.009782ms) +✔ class drift gated to within-role refuses before consumption (176.876849ms) +✔ class drift cross-role to within-role refuses before consumption (170.039319ms) +✔ class drift within-role to gated refuses before consumption (137.135281ms) +✔ class drift within-role to cross-role refuses before consumption (136.866615ms) +✔ message.send consumes approval and prevents a later send or authorize (167.694291ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (183.895777ms) +✔ creates private WAL store and excludes a second writer until explicit close (113.952233ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (161.614455ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (172.22129ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (142.708781ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (97.015376ms) +✔ async transactions refuse before invoking their function (80.146314ms) +✔ recordTask keeps sync reads and a role write apart (156.50087ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (94.560023ms) +✔ a bad entry refuses the whole record (101.842108ms) +✔ taskView reads the projection for one business (119.992871ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (205.510593ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (394.392771ms) +✔ without an adapter the server refuses every task verb (172.702074ms) +✔ the runtime refuses an invalid adapter and closes a valid one (164.929737ms) +✔ the process loads the S3 adapter from plain-data trackers (228.637175ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (131.968242ms) +✔ two wire claims serialize; a lost reply never automatically retries (164.064252ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (111.581399ms) +✔ client preserves UTF-8 when a response divides a multibyte character (11.575875ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (124.775233ms) +ℹ tests 74 +ℹ suites 0 +ℹ pass 73 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2252.198861 + +✖ failing tests: + +test at packages/bus/tests/end-launch.test.mjs:73:1 +✖ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (143.641995ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/bus/tests/end-launch.test.mjs:80:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + expected: /unknown-run/, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-cli.txt new file mode 100644 index 00000000..afa7ff90 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mb-runs-set-early-cli.txt @@ -0,0 +1,93 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (109.326208ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (131.173011ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (95.380715ms) +✔ decide prints a declining choice as declining (101.261199ms) +✔ an unknown outcome is reported once and never resent (90.873521ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (82.553178ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (65.663277ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (63.161817ms) +✔ every human command refuses inside an agent run before it touches the bus (71.517855ms) +✔ usage errors exit 4; no business and no host is a usage error (64.771744ms) +✔ agents and tasks print through the broker (81.235777ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.491731ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (42.390466ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (31.817917ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (29.31364ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (29.235237ms) +✔ a business without tracker.baseUrl gets no trackers entry (27.976293ms) +✔ an unknown business and a broken system config refuse with exit 3 (54.504904ms) +✔ empty views say so (0.643051ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.820582ms) +✔ tasks print the tracker fields the snapshot carries (0.12945ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (878.852777ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (193.435769ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (109.149738ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1116.029982ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (297.363627ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (136.080863ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (211.117221ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (152.098598ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (105.739164ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (168.47933ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (109.013095ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (161.766518ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.205588ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.038679ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (220.245857ms) +✔ bus start refuses with exit 3 without a notifier config (87.740202ms) +✔ bus start runs until bus stop; status reports it while it runs (648.344607ms) +✔ bus-service.sh renders the unit and installs it into a given directory (29.199759ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1236.950849ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (590.206319ms) +✔ a runner that stops at once ends its launch with the runner's reason (196.00187ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (654.407244ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3602.247558ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (114.232121ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (113.625918ms) +✔ a launch client that never closes its side doesn't hold the host's close (123.424341ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1225.669061ms) +✔ zoned uses the IANA zone across DST (13.117943ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (98.860357ms) +✔ two blocking decisions get two DMs with different nonces (116.067216ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.183696ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (100.784484ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (95.212487ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (90.646063ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (84.25297ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (132.969396ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (106.092917ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (90.088021ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (75.007703ms) +✔ an inbox read failure is logged and the next poll retries (0.632172ms) +✔ no Discord id reaches the journal or the log (53.143761ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (10.619939ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (14.032303ms) +✔ the journal: a whole file that is one torn line truncates to empty (17.698663ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.524247ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.532295ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.788243ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.32782ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.431233ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.343949ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.439468ms) +✔ digest content stays within Discord's 2000 characters (0.243767ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.696929ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (364.080148ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (32.109277ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2144.091683ms) +✔ busExit and refuseInsideAgent (0.423576ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (204.839812ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1133.088597ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (91.219169ms) +✔ launches off and on go to the broker and change the business's launch state (86.894265ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (50.775287ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 83 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7932.873171 diff --git a/agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-bus.txt b/agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-bus.txt new file mode 100644 index 00000000..5a882279 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-bus.txt @@ -0,0 +1,102 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (171.830893ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (266.890225ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (268.626968ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (176.486715ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (167.734881ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (216.140206ms) +✔ task action subjects and linked decision trail are complete and ordered (198.424735ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (129.416451ms) +✔ business isolation includes inherited object names and cross-business message references (177.414367ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (269.053033ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (118.687513ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (201.774081ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (113.973709ms) +✔ both arbiters require human resolution when their cross-role route is themselves (214.525848ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.488662ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (3.589048ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.753701ms) +✔ expiry refuses use and env references never become client data (0.688026ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.589672ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.487968ms) +✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (131.846264ms) +✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (124.349879ms) +✖ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (150.140782ms) +✔ endLaunch leaves a claim another run took alone (172.127339ms) +✔ refuse records action.refused against the caller with the code only (143.0897ms) +✔ launches off refuses role.launch with launch-revoked until launches on (187.119729ms) +✔ broker process: launch ops authorize role.launch, record refusals and end runs (276.999449ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.245578ms) +✔ process reader gets own kernel identity without exposing environment values (0.58204ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.855876ms) +✔ real pid 1 remains inspectable when its environment is protected (0.386748ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (192.364372ms) +✔ missing and foreign-business citations refuse and roll back message and grant (187.548757ms) +✔ cross-role sends still need a matching resolved decision and consume it once (244.60423ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (182.339188ms) +✔ startup token refusal returns safe code without value or partial listening broker (33.831434ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (177.750375ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (169.468399ms) +✔ trusted host registers later launches; socket clients never have a registration verb (185.783447ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (34.587524ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (162.341403ms) +✔ v3b prototype refusals, views and append-only mutations (1055.017991ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (251.168363ms) +✔ another run cannot consume an approval; a failed check leaves it usable (229.971363ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (156.332039ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (324.651503ms) +✔ class drift gated to cross-role refuses before consumption (247.34708ms) +✔ class drift cross-role to gated refuses before consumption (274.520898ms) +✔ class drift gated to within-role refuses before consumption (221.156719ms) +✔ class drift cross-role to within-role refuses before consumption (213.99909ms) +✔ class drift within-role to gated refuses before consumption (175.214444ms) +✔ class drift within-role to cross-role refuses before consumption (171.288068ms) +✔ message.send consumes approval and prevents a later send or authorize (194.291447ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (251.190733ms) +✔ creates private WAL store and excludes a second writer until explicit close (129.296689ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (173.160026ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (202.099112ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (177.503462ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (111.657608ms) +✔ async transactions refuse before invoking their function (91.472046ms) +✔ recordTask keeps sync reads and a role write apart (187.97079ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (102.181955ms) +✔ a bad entry refuses the whole record (123.207407ms) +✔ taskView reads the projection for one business (127.272093ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (255.029589ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (411.452538ms) +✔ without an adapter the server refuses every task verb (257.718944ms) +✔ the runtime refuses an invalid adapter and closes a valid one (209.735419ms) +✔ the process loads the S3 adapter from plain-data trackers (252.195976ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (158.941898ms) +✔ two wire claims serialize; a lost reply never automatically retries (171.097842ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (127.164905ms) +✔ client preserves UTF-8 when a response divides a multibyte character (11.630703ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (126.014105ms) +ℹ tests 74 +ℹ suites 0 +ℹ pass 73 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2792.42535 + +✖ failing tests: + +test at packages/bus/tests/end-launch.test.mjs:73:1 +✖ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (150.140782ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/bus/tests/end-launch.test.mjs:79:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + expected: /run-ended/, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-cli.txt new file mode 100644 index 00000000..981a47ed --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mc-no-run-ended-cli.txt @@ -0,0 +1,125 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (309.634682ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (118.696368ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (101.662485ms) +✔ decide prints a declining choice as declining (122.444104ms) +✔ an unknown outcome is reported once and never resent (101.437057ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (102.531101ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (78.700896ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (71.444982ms) +✔ every human command refuses inside an agent run before it touches the bus (90.620535ms) +✔ usage errors exit 4; no business and no host is a usage error (98.828565ms) +✔ agents and tasks print through the broker (83.722031ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.23847ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (42.249629ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (29.844931ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (30.706006ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (33.528891ms) +✔ a business without tracker.baseUrl gets no trackers entry (32.264963ms) +✔ an unknown business and a broken system config refuse with exit 3 (54.009647ms) +✔ empty views say so (1.298906ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.814006ms) +✔ tasks print the tracker fields the snapshot carries (0.126892ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (962.810981ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (234.767814ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (133.006335ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1152.351361ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (268.524267ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (115.563058ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (180.902229ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (146.725849ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (106.066059ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (149.936151ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (101.206974ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (168.784935ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.051318ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.123482ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (206.898871ms) +✔ bus start refuses with exit 3 without a notifier config (88.448546ms) +✔ bus start runs until bus stop; status reports it while it runs (659.579674ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.585499ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1380.180464ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (595.124302ms) +✔ a runner that stops at once ends its launch with the runner's reason (197.108079ms) +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (636.033001ms) +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3571.042112ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (111.741854ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (119.443998ms) +✔ a launch client that never closes its side doesn't hold the host's close (125.254109ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1248.132557ms) +✔ zoned uses the IANA zone across DST (14.293042ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (290.6152ms) +✔ two blocking decisions get two DMs with different nonces (119.187282ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.176503ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (98.07324ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (112.432257ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (101.534598ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (95.906933ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (163.15469ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (116.407993ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (118.11592ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (83.370822ms) +✔ an inbox read failure is logged and the next poll retries (0.600193ms) +✔ no Discord id reaches the journal or the log (83.535018ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (12.333322ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (16.341221ms) +✔ the journal: a whole file that is one torn line truncates to empty (20.335716ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.557474ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.601419ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.870956ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.306264ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.460335ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.357867ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.416711ms) +✔ digest content stays within Discord's 2000 characters (0.25743ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.146437ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (449.222442ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (36.80007ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2199.150483ms) +✔ busExit and refuseInsideAgent (0.395606ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (396.000167ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1118.182091ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (62.336186ms) +✔ launches off and on go to the broker and change the business's launch state (61.397539ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (60.223823ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 81 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 8067.334926 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (636.033001ms) + AssertionError [ERR_ASSERTION]: run r9499854d6538 (pm) from an earlier host ended: host-lost + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:308: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: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3571.042112ms) + AssertionError [ERR_ASSERTION]: run r77e26813e581 (pm) from an earlier host ended: host-lost + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:308: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: false, + expected: true, + operator: '==', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Md-no-instance-running-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Md-no-instance-running-cli.txt new file mode 100644 index 00000000..91e7f569 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Md-no-instance-running-cli.txt @@ -0,0 +1,138 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (129.93156ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (123.884746ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (115.843032ms) +✔ decide prints a declining choice as declining (109.117196ms) +✔ an unknown outcome is reported once and never resent (100.830598ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (114.11726ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (102.97002ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (77.223689ms) +✔ every human command refuses inside an agent run before it touches the bus (101.503502ms) +✔ usage errors exit 4; no business and no host is a usage error (106.31844ms) +✔ agents and tasks print through the broker (72.739189ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.675268ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (39.385037ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (32.220313ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (30.171371ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (28.720425ms) +✔ a business without tracker.baseUrl gets no trackers entry (27.442203ms) +✔ an unknown business and a broken system config refuse with exit 3 (52.59881ms) +✔ empty views say so (0.645292ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.874932ms) +✔ tasks print the tracker fields the snapshot carries (0.164372ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (900.189427ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (219.873017ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (171.323865ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1158.496837ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (258.774993ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (158.586124ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (221.013832ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (146.920394ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (110.590833ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (149.360621ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (99.870717ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (155.494107ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.312045ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.47245ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (204.049139ms) +✔ bus start refuses with exit 3 without a notifier config (88.775695ms) +✔ bus start runs until bus stop; status reports it while it runs (658.132981ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.081069ms) +✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (254.931601ms) +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (565.632459ms) +✔ a runner that stops at once ends its launch with the runner's reason (250.090841ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (836.404787ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3654.783453ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (125.211009ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (126.870758ms) +✔ a launch client that never closes its side doesn't hold the host's close (140.855213ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1228.817507ms) +✔ zoned uses the IANA zone across DST (18.394486ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (120.140473ms) +✔ two blocking decisions get two DMs with different nonces (118.392369ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.185755ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (107.742134ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (105.558334ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (112.281093ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (121.790359ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (142.212246ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (136.241889ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (130.127659ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (71.086624ms) +✔ an inbox read failure is logged and the next poll retries (0.656521ms) +✔ no Discord id reaches the journal or the log (90.37866ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (34.647012ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (34.271841ms) +✔ the journal: a whole file that is one torn line truncates to empty (12.166984ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.775038ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.556419ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.752855ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.306085ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.449951ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.370948ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.443611ms) +✔ digest content stays within Discord's 2000 characters (0.297002ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.662184ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (372.331809ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (34.570222ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2155.142118ms) +✔ busExit and refuseInsideAgent (0.487416ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (209.004196ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1134.886407ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (76.368041ms) +✔ launches off and on go to the broker and change the business's launch state (93.532466ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (244.59335ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 81 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7267.769547 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:106:1 +✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (254.931601ms) + AssertionError [ERR_ASSERTION]: The input did not match the regular expression /PM launch refused: instance-running/. Input: + + 'CliError: PM launch refused: capacity-full (opus sessions: 1 of 1)' + + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:138:3) + 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: CliError: PM launch refused: capacity-full (opus sessions: 1 of 1) + at file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/launcher.mjs:297:15 + at process.processTicksAndRejections (node:internal/process/task_queues:104:5), + expected: /PM launch refused: instance-running/, + operator: 'rejects', + diff: 'simple' + } + +test at packages/cli/tests/launcher.test.mjs:176:1 +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (565.632459ms) + AssertionError [ERR_ASSERTION]: coder + + actual - expected + + { + + error: 'capacity-full', + - error: 'instance-running', + ok: false + } + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:210: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: { ok: false, error: 'capacity-full' }, + expected: { ok: false, error: 'instance-running' }, + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Me-no-authorize-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Me-no-authorize-cli.txt new file mode 100644 index 00000000..eb147b88 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Me-no-authorize-cli.txt @@ -0,0 +1,113 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (110.686948ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (106.884162ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (95.564226ms) +✔ decide prints a declining choice as declining (93.308121ms) +✔ an unknown outcome is reported once and never resent (85.748876ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (111.392918ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (105.034871ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (79.484863ms) +✔ every human command refuses inside an agent run before it touches the bus (87.835327ms) +✔ usage errors exit 4; no business and no host is a usage error (61.843656ms) +✔ agents and tasks print through the broker (90.634341ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.22131ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (43.213645ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (31.637132ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (33.482925ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (31.454924ms) +✔ a business without tracker.baseUrl gets no trackers entry (29.454118ms) +✔ an unknown business and a broken system config refuse with exit 3 (49.114362ms) +✔ empty views say so (0.677347ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.829279ms) +✔ tasks print the tracker fields the snapshot carries (0.160354ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (870.011264ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (226.229412ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (143.156673ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1148.507506ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (220.406667ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (114.526272ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (167.292875ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (266.663134ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (101.970192ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (151.784469ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (114.271243ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (161.599832ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.540049ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.054233ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (223.307199ms) +✔ bus start refuses with exit 3 without a notifier config (111.482986ms) +✔ bus start runs until bus stop; status reports it while it runs (667.080551ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.581949ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (247.145481ms) +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (625.779647ms) +✔ a runner that stops at once ends its launch with the runner's reason (258.95991ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (655.037464ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3608.481612ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (100.961775ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (121.835281ms) +✔ a launch client that never closes its side doesn't hold the host's close (126.622099ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1246.462493ms) +✔ zoned uses the IANA zone across DST (17.863134ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (105.795241ms) +✔ two blocking decisions get two DMs with different nonces (105.323774ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.169749ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (92.153757ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (97.900989ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (95.877773ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (108.83029ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (160.561265ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (125.587856ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (110.630567ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (67.640966ms) +✔ an inbox read failure is logged and the next poll retries (0.662238ms) +✔ no Discord id reaches the journal or the log (75.613047ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (11.221849ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (34.184143ms) +✔ the journal: a whole file that is one torn line truncates to empty (27.674215ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (3.778543ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.554957ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.878846ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.298944ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.470225ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.336303ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.433947ms) +✔ digest content stays within Discord's 2000 characters (0.259131ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.633584ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (352.423936ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (32.127121ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2130.283949ms) +✔ busExit and refuseInsideAgent (0.423043ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (197.999404ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1120.766392ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (70.7995ms) +✔ launches off and on go to the broker and change the business's launch state (53.704552ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (58.667476ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7075.234251 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:176:1 +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (625.779647ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:218: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: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mf-no-exit2-wrapper-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mf-no-exit2-wrapper-harness.txt new file mode 100644 index 00000000..24f71629 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mf-no-exit2-wrapper-harness.txt @@ -0,0 +1,86 @@ +✔ sessionModel: agent vars win, then the system's execution settings (12.610037ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.425938ms) +✖ a claude-code bundle adds the wrapped gate hook and the MCP config (3.569669ms) +✔ a bundle is written once: an existing file refuses (2.483043ms) +✔ a path with a single quote can't go into the hook command (2.194773ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (134.321564ms) +✔ a missing or wrong policy, or a bad event, exits 2 (94.191866ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1102.478386ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (861.169402ms) +✔ claude: the hook alone blocks a path outside the workspace (463.346559ms) +✔ claude: a second turn resumes the first turn's session (699.909671ms) +✔ claude adapter: --restricted is always passed (5.009911ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (731.002227ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.483698ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (3.524415ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.859553ms) +✔ file tool paths must resolve inside the workspace (1.61983ms) +✔ pi's own path normalisation can't be used to step out (1.381501ms) +✔ a symlink inside the workspace that points out is outside (1.216654ms) +✔ a dangling symlink is refused at any depth, in both harnesses (4.524108ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (20.186307ms) +✔ other spellings cover directories, dangling links and pi's cwd (2.49432ms) +✔ claude path fields per tool (0.895082ms) +✔ glob patterns stay inside the workspace (0.957675ms) +✔ a path that can't be checked is blocked (0.481683ms) +✔ initialize, ping and tools/list (57.350848ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (37.26344ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (35.653847ms) +✔ a missing argument is a usage error (28.896466ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (418.424476ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (379.103331ms) +✔ pi: a read is refused when pi would open another spelling outside (337.38634ms) +✔ pi: a missing extension refuses before any model call (7.295878ms) +✔ pi: an extension without its configuration fails pi's start (259.256401ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.638607ms) +✔ turnRequest names the sender, class, reply and decision (0.460845ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (259.921518ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (129.664413ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (431.771978ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1312.831889ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (165.959867ms) +✔ founder credentials stop before the claim (20) (174.448279ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (195.01728ms) +✔ the launch ending under a running session exits 22 (149.181744ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (245.012982ms) +✔ a broker that is down at the claim exits 23, not 21 (88.047572ms) +✔ no capability, or a malformed one, on stdin exits 2 (184.432872ms) +✔ a missing or malformed policy exits 2 before the claim (129.019332ms) +✔ the PM gets launch, its task verbs and the reads (8.679382ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.972469ms) +✔ launch only when the business's launch block names the instance as launcher (2.254721ms) +✔ an action outside the instance's authority has no tool (2.314311ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (10.564592ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 52 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10442.013918 + +✖ failing tests: + +test at packages/harness/tests/bundle.test.mjs:70:1 +✖ a claude-code bundle adds the wrapped gate hook and the MCP config (3.569669ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + actual - expected + + + "timeout -k 2 10 '/usr/bin/node' '/home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/src/claude-gate.mjs' '/home/jwoltje/darkwing-scratch/tmp/mosaic-harness-A4Asfi/bundle/policy.json'" + - "timeout -k 2 10 '/usr/bin/node' '/home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/src/claude-gate.mjs' '/home/jwoltje/darkwing-scratch/tmp/mosaic-harness-A4Asfi/bundle/policy.json' || exit 2" + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/bundle.test.mjs:78:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: "timeout -k 2 10 '/usr/bin/node' '/home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/src/claude-gate.mjs' '/home/jwoltje/darkwing-scratch/tmp/mosaic-harness-A4Asfi/bundle/policy.json'", + expected: "timeout -k 2 10 '/usr/bin/node' '/home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/src/claude-gate.mjs' '/home/jwoltje/darkwing-scratch/tmp/mosaic-harness-A4Asfi/bundle/policy.json' || exit 2", + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-cli.txt new file mode 100644 index 00000000..80418bf8 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-cli.txt @@ -0,0 +1,93 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (136.02924ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (132.966972ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (119.457874ms) +✔ decide prints a declining choice as declining (130.32497ms) +✔ an unknown outcome is reported once and never resent (101.355712ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (90.329289ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (93.008445ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (85.138662ms) +✔ every human command refuses inside an agent run before it touches the bus (78.813706ms) +✔ usage errors exit 4; no business and no host is a usage error (77.799996ms) +✔ agents and tasks print through the broker (80.290372ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.201216ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (46.429049ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (33.390555ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (31.116965ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (31.667236ms) +✔ a business without tracker.baseUrl gets no trackers entry (31.072837ms) +✔ an unknown business and a broken system config refuse with exit 3 (51.43796ms) +✔ empty views say so (0.788334ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.842352ms) +✔ tasks print the tracker fields the snapshot carries (0.151138ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (918.644102ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (227.493155ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (141.738131ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1143.209858ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (227.331454ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (111.262419ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (161.185092ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (152.757124ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (102.147396ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (148.695414ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (106.338995ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (161.913554ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.115732ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.001141ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (211.547824ms) +✔ bus start refuses with exit 3 without a notifier config (87.064124ms) +✔ bus start runs until bus stop; status reports it while it runs (658.300972ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.936662ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (298.245484ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (665.262206ms) +✔ a runner that stops at once ends its launch with the runner's reason (235.6999ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (660.765268ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3579.756921ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (111.070392ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (121.421911ms) +✔ a launch client that never closes its side doesn't hold the host's close (122.58027ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1238.855638ms) +✔ zoned uses the IANA zone across DST (22.164907ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (141.365538ms) +✔ two blocking decisions get two DMs with different nonces (126.774437ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.170395ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (113.15991ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (122.62483ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (110.402208ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (92.432026ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (142.723337ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (124.596445ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (111.957527ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (81.387632ms) +✔ an inbox read failure is logged and the next poll retries (0.632008ms) +✔ no Discord id reaches the journal or the log (69.92432ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (17.977804ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (37.617959ms) +✔ the journal: a whole file that is one torn line truncates to empty (17.168561ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.301834ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.577697ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.847792ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.321022ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.472741ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.352923ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.419505ms) +✔ digest content stays within Discord's 2000 characters (0.249459ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.209763ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (391.009885ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (35.007297ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2150.619757ms) +✔ busExit and refuseInsideAgent (0.446199ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (236.272608ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1138.287074ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (72.243845ms) +✔ launches off and on go to the broker and change the business's launch state (70.005017ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (49.371667ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 83 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7126.804868 diff --git a/agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-harness.txt new file mode 100644 index 00000000..a73b7a41 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mg-no-founder-check-harness.txt @@ -0,0 +1,97 @@ +✔ sessionModel: agent vars win, then the system's execution settings (11.177806ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (5.252937ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.918977ms) +✔ a bundle is written once: an existing file refuses (2.667274ms) +✔ a path with a single quote can't go into the hook command (2.474121ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (149.860923ms) +✔ a missing or wrong policy, or a bad event, exits 2 (80.513977ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1087.944389ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (756.080075ms) +✔ claude: the hook alone blocks a path outside the workspace (468.03756ms) +✔ claude: a second turn resumes the first turn's session (686.572807ms) +✔ claude adapter: --restricted is always passed (5.208836ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (712.744346ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.437396ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (3.76221ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.832287ms) +✔ file tool paths must resolve inside the workspace (1.253709ms) +✔ pi's own path normalisation can't be used to step out (1.179842ms) +✔ a symlink inside the workspace that points out is outside (1.019438ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.701157ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (29.193137ms) +✔ other spellings cover directories, dangling links and pi's cwd (7.676756ms) +✔ claude path fields per tool (0.905772ms) +✔ glob patterns stay inside the workspace (0.978329ms) +✔ a path that can't be checked is blocked (0.637371ms) +✔ initialize, ping and tools/list (54.963032ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (40.53307ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (29.9518ms) +✔ a missing argument is a usage error (26.739266ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (369.845735ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (305.775115ms) +✔ pi: a read is refused when pi would open another spelling outside (321.07066ms) +✔ pi: a missing extension refuses before any model call (7.216682ms) +✔ pi: an extension without its configuration fails pi's start (262.950669ms) +✖ founderCheck: founder variables, then a needed service without a usable token (1.768828ms) +✔ turnRequest names the sender, class, reply and decision (0.255406ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (254.803001ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (106.271699ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (409.851372ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1300.337486ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (151.104932ms) +✖ founder credentials stop before the claim (20) (60055.438549ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (205.220258ms) +✔ the launch ending under a running session exits 22 (137.168395ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (240.044539ms) +✔ a broker that is down at the claim exits 23, not 21 (103.647221ms) +✔ no capability, or a malformed one, on stdin exits 2 (194.450239ms) +✔ a missing or malformed policy exits 2 before the claim (151.976732ms) +✔ the PM gets launch, its task verbs and the reads (9.204315ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.319111ms) +✔ launch only when the business's launch block names the instance as launcher (2.395528ms) +✔ an action outside the instance's authority has no tool (2.487739ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (11.73562ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 51 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 63410.518602 + +✖ failing tests: + +test at packages/harness/tests/runner.test.mjs:34:1 +✖ founderCheck: founder variables, then a needed service without a usable token (1.768828ms) + AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. Received type object (null) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:38:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.start (node:internal/test_runner/test:1262:17) + at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: null, + expected: /GITEA_TOKEN, SSH_AUTH_SOCK/, + operator: 'match', + diff: 'simple' + } + +test at packages/harness/tests/runner.test.mjs:261:1 +✖ founder credentials stop before the claim (20) (60055.438549ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + null !== 20 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:264: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: null, + expected: 20, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mh-no-close-sigkill-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mh-no-close-sigkill-cli.txt new file mode 100644 index 00000000..f53d0229 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mh-no-close-sigkill-cli.txt @@ -0,0 +1,102 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (118.041998ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (131.48971ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (105.398857ms) +✔ decide prints a declining choice as declining (113.679832ms) +✔ an unknown outcome is reported once and never resent (103.463596ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (101.546322ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (117.043323ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (94.753147ms) +✔ every human command refuses inside an agent run before it touches the bus (168.32314ms) +✔ usage errors exit 4; no business and no host is a usage error (116.968773ms) +✔ agents and tasks print through the broker (114.772476ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.031321ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (58.653922ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (37.003735ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (36.75054ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (38.472211ms) +✔ a business without tracker.baseUrl gets no trackers entry (31.429728ms) +✔ an unknown business and a broken system config refuse with exit 3 (61.848541ms) +✔ empty views say so (0.773262ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.001536ms) +✔ tasks print the tracker fields the snapshot carries (0.168093ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (916.629745ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (282.431038ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (151.459267ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1298.740463ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (267.882667ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (118.937925ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (163.746589ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (162.737307ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (100.238564ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (147.397721ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (99.55411ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (158.126607ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.58234ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.873188ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (216.76327ms) +✔ bus start refuses with exit 3 without a notifier config (89.840034ms) +✔ bus start runs until bus stop; status reports it while it runs (663.801122ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.911461ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1274.104991ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (641.495076ms) +✔ a runner that stops at once ends its launch with the runner's reason (193.157972ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (808.645751ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3805.213621ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (103.523055ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (116.463798ms) +✔ a launch client that never closes its side doesn't hold the host's close (116.200463ms) +✖ a runner that ignores SIGTERM is killed when the host closes (15219.09123ms) +✔ zoned uses the IANA zone across DST (25.789076ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (117.526082ms) +✔ two blocking decisions get two DMs with different nonces (119.271966ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.21734ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (86.435325ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (99.237842ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (102.191522ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (97.68472ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (166.19917ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (152.651257ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (187.218066ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (112.051392ms) +✔ an inbox read failure is logged and the next poll retries (0.655555ms) +✔ no Discord id reaches the journal or the log (106.117268ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (22.013075ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (50.417528ms) +✔ the journal: a whole file that is one torn line truncates to empty (29.165771ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.409732ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.584473ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.741691ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.303514ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.453615ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.350523ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.414634ms) +✔ digest content stays within Discord's 2000 characters (0.249579ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.994649ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (385.255221ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (44.005372ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2163.602099ms) +✔ busExit and refuseInsideAgent (0.399287ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (205.64473ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1125.955523ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (115.161917ms) +✔ launches off and on go to the broker and change the business's launch state (95.270653ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (69.6019ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 22364.750197 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:369:1 +✖ a runner that ignores SIGTERM is killed when the host closes (15219.09123ms) + Error: close took over 15000 ms + at Timeout._onTimeout (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:324:101) + at listOnTimeout (node:internal/timers:685:17) + at process.processTimers (node:internal/timers:618:7) diff --git a/agents/darkwing/work/s6-review/r3/mut/Mi-recover-no-kill-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mi-recover-no-kill-cli.txt new file mode 100644 index 00000000..961d6607 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mi-recover-no-kill-cli.txt @@ -0,0 +1,115 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (106.922712ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (127.453207ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (100.792147ms) +✔ decide prints a declining choice as declining (90.547205ms) +✔ an unknown outcome is reported once and never resent (77.914315ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (74.808302ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (73.514331ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (50.730691ms) +✔ every human command refuses inside an agent run before it touches the bus (57.950143ms) +✔ usage errors exit 4; no business and no host is a usage error (72.623963ms) +✔ agents and tasks print through the broker (70.264188ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.760366ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (57.164692ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (43.867609ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (44.641985ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (35.056691ms) +✔ a business without tracker.baseUrl gets no trackers entry (41.701591ms) +✔ an unknown business and a broken system config refuse with exit 3 (87.619271ms) +✔ empty views say so (1.207053ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.53345ms) +✔ tasks print the tracker fields the snapshot carries (0.247669ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (951.824825ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (222.425802ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (131.366495ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1192.983731ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (216.583457ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (111.043471ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (166.205089ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (154.807489ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (105.531871ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (156.826593ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (113.11359ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (166.141468ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.89589ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.758946ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (218.040413ms) +✔ bus start refuses with exit 3 without a notifier config (91.472452ms) +✔ bus start runs until bus stop; status reports it while it runs (658.450829ms) +✔ bus-service.sh renders the unit and installs it into a given directory (29.341383ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1297.970325ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (626.374663ms) +✔ a runner that stops at once ends its launch with the runner's reason (194.610384ms) +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (5418.855495ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3579.200989ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (100.682148ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (113.819595ms) +✔ a launch client that never closes its side doesn't hold the host's close (118.474258ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1220.180984ms) +✔ zoned uses the IANA zone across DST (22.285939ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (112.820655ms) +✔ two blocking decisions get two DMs with different nonces (113.812033ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.333902ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (106.433816ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (93.823646ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (79.798724ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (69.07935ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (151.693102ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (113.252853ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (87.064619ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (51.654076ms) +✔ an inbox read failure is logged and the next poll retries (0.698838ms) +✔ no Discord id reaches the journal or the log (52.085441ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (10.582447ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (28.364707ms) +✔ the journal: a whole file that is one torn line truncates to empty (17.507019ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.661067ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.840219ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.577946ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.403361ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.628308ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.421861ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.512618ms) +✔ digest content stays within Discord's 2000 characters (0.313904ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.272496ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (412.633737ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (45.45632ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2190.732641ms) +✔ busExit and refuseInsideAgent (0.51342ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (197.19843ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1110.244488ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (57.906341ms) +✔ launches off and on go to the broker and change the business's launch state (118.525037ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (61.227434ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 12781.601532 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (5418.855495ms) + AssertionError [ERR_ASSERTION]: the old session process is gone + + actual - expected + + + '293672659' + - null + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:288: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: '293672659', + expected: null, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mj-no-stdin-cap-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mj-no-stdin-cap-harness.txt new file mode 100644 index 00000000..3ee52036 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mj-no-stdin-cap-harness.txt @@ -0,0 +1,82 @@ +✔ sessionModel: agent vars win, then the system's execution settings (10.806166ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (5.589457ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.22644ms) +✔ a bundle is written once: an existing file refuses (2.363335ms) +✔ a path with a single quote can't go into the hook command (2.224508ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (122.076692ms) +✔ a missing or wrong policy, or a bad event, exits 2 (83.496256ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1102.964932ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (749.26968ms) +✔ claude: the hook alone blocks a path outside the workspace (553.235913ms) +✔ claude: a second turn resumes the first turn's session (749.117347ms) +✔ claude adapter: --restricted is always passed (5.035892ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (747.57096ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.276715ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (2.883895ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.67454ms) +✔ file tool paths must resolve inside the workspace (0.923096ms) +✔ pi's own path normalisation can't be used to step out (0.878111ms) +✔ a symlink inside the workspace that points out is outside (0.713036ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.265306ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (12.755782ms) +✔ other spellings cover directories, dangling links and pi's cwd (2.289035ms) +✔ claude path fields per tool (0.889096ms) +✔ glob patterns stay inside the workspace (0.983727ms) +✔ a path that can't be checked is blocked (0.418088ms) +✔ initialize, ping and tools/list (42.646925ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (32.786393ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (35.510704ms) +✔ a missing argument is a usage error (30.037917ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (370.391087ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (333.500162ms) +✔ pi: a read is refused when pi would open another spelling outside (361.491201ms) +✔ pi: a missing extension refuses before any model call (8.198461ms) +✔ pi: an extension without its configuration fails pi's start (333.092688ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.232397ms) +✔ turnRequest names the sender, class, reply and decision (0.177879ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (249.610514ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (112.946053ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (416.29377ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1374.427902ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (172.084448ms) +✔ founder credentials stop before the claim (20) (172.24744ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (191.294639ms) +✔ the launch ending under a running session exits 22 (143.637834ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (246.434944ms) +✔ a broker that is down at the claim exits 23, not 21 (88.931511ms) +✖ no capability, or a malformed one, on stdin exits 2 (60159.927178ms) +✔ a missing or malformed policy exits 2 before the claim (150.006936ms) +✔ the PM gets launch, its task verbs and the reads (8.859602ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.132664ms) +✔ launch only when the business's launch block names the instance as launcher (2.095966ms) +✔ an action outside the instance's authority has no tool (2.835666ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (8.09316ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 52 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 63573.672105 + +✖ failing tests: + +test at packages/harness/tests/runner.test.mjs:313:1 +✖ no capability, or a malformed one, on stdin exits 2 (60159.927178ms) + AssertionError [ERR_ASSERTION]: runner: demo/coder claimed by run coder-run + + + null !== 2 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:320: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: null, + expected: 2, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mk-launch-line-max-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mk-launch-line-max-cli.txt new file mode 100644 index 00000000..0fe088f5 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mk-launch-line-max-cli.txt @@ -0,0 +1,102 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (118.780918ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (123.076137ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (100.463479ms) +✔ decide prints a declining choice as declining (106.312948ms) +✔ an unknown outcome is reported once and never resent (80.512806ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (98.603993ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (82.154826ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (64.674468ms) +✔ every human command refuses inside an agent run before it touches the bus (86.504502ms) +✔ usage errors exit 4; no business and no host is a usage error (106.585889ms) +✔ agents and tasks print through the broker (79.843314ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.563511ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (47.033594ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (35.560285ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (37.829148ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (34.228994ms) +✔ a business without tracker.baseUrl gets no trackers entry (27.989445ms) +✔ an unknown business and a broken system config refuse with exit 3 (59.357376ms) +✔ empty views say so (1.003416ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.268413ms) +✔ tasks print the tracker fields the snapshot carries (0.192113ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (897.887185ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (231.829637ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (123.871284ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1152.416722ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (233.027102ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (117.315903ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (164.653757ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (151.64516ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (105.118583ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (146.765951ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (107.101812ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (163.429516ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.140623ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.866959ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (208.591307ms) +✔ bus start refuses with exit 3 without a notifier config (85.851102ms) +✔ bus start runs until bus stop; status reports it while it runs (655.59803ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.938059ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (276.144899ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (660.381461ms) +✔ a runner that stops at once ends its launch with the runner's reason (247.131579ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (726.717899ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3608.255401ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (103.392709ms) +✖ an over-long launch request is refused at once, not at the 10 s idle timeout (3123.508123ms) +✔ a launch client that never closes its side doesn't hold the host's close (122.848856ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1227.396355ms) +✔ zoned uses the IANA zone across DST (25.44643ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (123.134657ms) +✔ two blocking decisions get two DMs with different nonces (116.563319ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.179118ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (107.062052ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (100.127376ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (90.695894ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (93.29856ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (130.339884ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (120.291521ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (110.318126ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (89.460537ms) +✔ an inbox read failure is logged and the next poll retries (0.787951ms) +✔ no Discord id reaches the journal or the log (64.874891ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (11.598635ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (25.355305ms) +✔ the journal: a whole file that is one torn line truncates to empty (13.765272ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.789483ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.703771ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.84146ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.527478ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.790389ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.474219ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.568551ms) +✔ digest content stays within Discord's 2000 characters (0.303117ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.718683ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (365.892636ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (41.142536ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2155.789347ms) +✔ busExit and refuseInsideAgent (0.425706ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (194.548365ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1104.428525ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (85.842231ms) +✔ launches off and on go to the broker and change the business's launch state (61.780226ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (37.627993ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10175.91753 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:326:1 +✖ an over-long launch request is refused at once, not at the 10 s idle timeout (3123.508123ms) + Error: the refusal took over 3000 ms + at Timeout._onTimeout (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:324:101) + at listOnTimeout (node:internal/timers:685:17) + at process.processTimers (node:internal/timers:618:7) diff --git a/agents/darkwing/work/s6-review/r3/mut/Ml-gate-pattern-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Ml-gate-pattern-harness.txt new file mode 100644 index 00000000..3f53a905 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Ml-gate-pattern-harness.txt @@ -0,0 +1,85 @@ +✔ sessionModel: agent vars win, then the system's execution settings (11.36832ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.205659ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.452236ms) +✔ a bundle is written once: an existing file refuses (2.530905ms) +✔ a path with a single quote can't go into the hook command (1.901541ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (111.927229ms) +✔ a missing or wrong policy, or a bad event, exits 2 (81.333733ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1090.175498ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (788.043408ms) +✔ claude: the hook alone blocks a path outside the workspace (472.003038ms) +✔ claude: a second turn resumes the first turn's session (826.243142ms) +✔ claude adapter: --restricted is always passed (6.698491ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (786.753172ms) +✔ claude: a missing hook or MCP file refuses before claude starts (8.102132ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (3.244199ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.890437ms) +✔ file tool paths must resolve inside the workspace (1.14687ms) +✔ pi's own path normalisation can't be used to step out (0.964962ms) +✔ a symlink inside the workspace that points out is outside (0.822858ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.43826ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (16.468121ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.990483ms) +✔ claude path fields per tool (0.565463ms) +✖ glob patterns stay inside the workspace (1.294802ms) +✔ a path that can't be checked is blocked (0.393672ms) +✔ initialize, ping and tools/list (45.752355ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (31.151707ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (28.389758ms) +✔ a missing argument is a usage error (28.131437ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (364.598251ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (358.802531ms) +✔ pi: a read is refused when pi would open another spelling outside (338.681085ms) +✔ pi: a missing extension refuses before any model call (6.860284ms) +✔ pi: an extension without its configuration fails pi's start (261.93823ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.118099ms) +✔ turnRequest names the sender, class, reply and decision (0.258109ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (234.477454ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (115.414823ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (426.109041ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1298.43309ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (157.45163ms) +✔ founder credentials stop before the claim (20) (199.201503ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (217.716212ms) +✔ the launch ending under a running session exits 22 (150.417351ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (257.340121ms) +✔ a broker that is down at the claim exits 23, not 21 (85.225358ms) +✔ no capability, or a malformed one, on stdin exits 2 (193.755794ms) +✔ a missing or malformed policy exits 2 before the claim (130.745018ms) +✔ the PM gets launch, its task verbs and the reads (6.945692ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.982143ms) +✔ launch only when the business's launch block names the instance as launcher (1.498125ms) +✔ an action outside the instance's authority has no tool (1.658447ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (8.400448ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 52 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10344.683194 + +✖ failing tests: + +test at packages/harness/tests/gate.test.mjs:171:1 +✖ glob patterns stay inside the workspace (1.294802ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at blocked (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:19:10) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:177:5) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-bus.txt b/agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-bus.txt new file mode 100644 index 00000000..e63b6537 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-bus.txt @@ -0,0 +1,102 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (145.699342ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (246.753955ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (242.099071ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (154.800709ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (152.866183ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (143.13816ms) +✔ task action subjects and linked decision trail are complete and ordered (272.254567ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (111.423704ms) +✔ business isolation includes inherited object names and cross-business message references (219.984118ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (435.026479ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (125.65938ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (204.075537ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (113.966122ms) +✔ both arbiters require human resolution when their cross-role route is themselves (183.453882ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.308315ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.770669ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.834462ms) +✔ expiry refuses use and env references never become client data (0.858583ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.94638ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.59084ms) +✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (116.677841ms) +✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (131.158771ms) +✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (147.524656ms) +✔ endLaunch leaves a claim another run took alone (161.634957ms) +✔ refuse records action.refused against the caller with the code only (125.876016ms) +✖ launches off refuses role.launch with launch-revoked until launches on (142.300893ms) +✔ broker process: launch ops authorize role.launch, record refusals and end runs (218.862179ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (4.607107ms) +✔ process reader gets own kernel identity without exposing environment values (0.740531ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.032764ms) +✔ real pid 1 remains inspectable when its environment is protected (0.405112ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (184.174358ms) +✔ missing and foreign-business citations refuse and roll back message and grant (169.894038ms) +✔ cross-role sends still need a matching resolved decision and consume it once (226.260541ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (169.671805ms) +✔ startup token refusal returns safe code without value or partial listening broker (37.78021ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (175.279268ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (167.28754ms) +✔ trusted host registers later launches; socket clients never have a registration verb (167.034327ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (36.525098ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (154.714552ms) +✔ v3b prototype refusals, views and append-only mutations (950.500775ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (234.901997ms) +✔ another run cannot consume an approval; a failed check leaves it usable (202.680739ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (159.001809ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (284.633069ms) +✔ class drift gated to cross-role refuses before consumption (169.400824ms) +✔ class drift cross-role to gated refuses before consumption (304.313418ms) +✔ class drift gated to within-role refuses before consumption (205.104985ms) +✔ class drift cross-role to within-role refuses before consumption (247.99929ms) +✔ class drift within-role to gated refuses before consumption (366.042302ms) +✔ class drift within-role to cross-role refuses before consumption (170.491063ms) +✔ message.send consumes approval and prevents a later send or authorize (192.831049ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (206.596535ms) +✔ creates private WAL store and excludes a second writer until explicit close (113.23738ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (162.625023ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (167.236319ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (170.327072ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (93.442914ms) +✔ async transactions refuse before invoking their function (80.944013ms) +✔ recordTask keeps sync reads and a role write apart (160.829393ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (107.353806ms) +✔ a bad entry refuses the whole record (101.799967ms) +✔ taskView reads the projection for one business (116.999697ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (233.715142ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (395.045161ms) +✔ without an adapter the server refuses every task verb (314.479348ms) +✔ the runtime refuses an invalid adapter and closes a valid one (253.582773ms) +✔ the process loads the S3 adapter from plain-data trackers (438.173741ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (128.222648ms) +✔ two wire claims serialize; a lost reply never automatically retries (174.922863ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (100.447962ms) +✔ client preserves UTF-8 when a response divides a multibyte character (11.6814ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (126.768521ms) +ℹ tests 74 +ℹ suites 0 +ℹ pass 73 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2840.450299 + +✖ failing tests: + +test at packages/bus/tests/end-launch.test.mjs:121:1 +✖ launches off refuses role.launch with launch-revoked until launches on (142.300893ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/bus/tests/end-launch.test.mjs:128:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + expected: { code: 'launch-revoked' }, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-cli.txt new file mode 100644 index 00000000..7a9c486f --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mm-no-revoke-cli.txt @@ -0,0 +1,93 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (116.084453ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (112.71306ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (88.907075ms) +✔ decide prints a declining choice as declining (102.731262ms) +✔ an unknown outcome is reported once and never resent (82.6584ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (77.896147ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (87.244191ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (64.800019ms) +✔ every human command refuses inside an agent run before it touches the bus (89.189575ms) +✔ usage errors exit 4; no business and no host is a usage error (85.043507ms) +✔ agents and tasks print through the broker (100.592684ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.47154ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (40.882137ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (31.727587ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (29.828881ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (30.275658ms) +✔ a business without tracker.baseUrl gets no trackers entry (30.976275ms) +✔ an unknown business and a broken system config refuse with exit 3 (54.670977ms) +✔ empty views say so (0.794286ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.809227ms) +✔ tasks print the tracker fields the snapshot carries (0.178193ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (878.377096ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (210.114362ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (136.033147ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1115.382486ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (280.244479ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (119.655705ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (185.49727ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (153.606984ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (96.180988ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (158.579061ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (103.798932ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (236.702273ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.447264ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.033679ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (212.11801ms) +✔ bus start refuses with exit 3 without a notifier config (90.943833ms) +✔ bus start runs until bus stop; status reports it while it runs (652.281238ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.593011ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1258.165054ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (600.175105ms) +✔ a runner that stops at once ends its launch with the runner's reason (194.615591ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (653.53918ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3577.296328ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (112.25964ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (111.277258ms) +✔ a launch client that never closes its side doesn't hold the host's close (117.054582ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1227.896809ms) +✔ zoned uses the IANA zone across DST (16.208878ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (108.609638ms) +✔ two blocking decisions get two DMs with different nonces (111.650159ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.164603ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (100.239534ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (92.743283ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (86.394699ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (84.569098ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (143.353265ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (135.165024ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (111.288824ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (90.682654ms) +✔ an inbox read failure is logged and the next poll retries (0.602397ms) +✔ no Discord id reaches the journal or the log (62.968792ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (10.759013ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (17.772911ms) +✔ the journal: a whole file that is one torn line truncates to empty (21.401862ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.084797ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.607215ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.316928ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.33592ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.439607ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.366572ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.415756ms) +✔ digest content stays within Discord's 2000 characters (0.260281ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.735602ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (353.867034ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (31.810971ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2132.463559ms) +✔ busExit and refuseInsideAgent (0.430704ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (198.014175ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1130.381849ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (66.758197ms) +✔ launches off and on go to the broker and change the business's launch state (70.160938ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (83.38562ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 83 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7927.816552 diff --git a/agents/darkwing/work/s6-review/r3/mut/Mn-recover-no-end-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mn-recover-no-end-cli.txt new file mode 100644 index 00000000..3556533d --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mn-recover-no-end-cli.txt @@ -0,0 +1,131 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (116.773968ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (122.480011ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (107.737052ms) +✔ decide prints a declining choice as declining (103.777574ms) +✔ an unknown outcome is reported once and never resent (80.798848ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (73.721538ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (94.10683ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (52.03352ms) +✔ every human command refuses inside an agent run before it touches the bus (69.858852ms) +✔ usage errors exit 4; no business and no host is a usage error (62.385421ms) +✔ agents and tasks print through the broker (87.371512ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.201847ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (38.45186ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (30.565261ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (32.79128ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (27.593361ms) +✔ a business without tracker.baseUrl gets no trackers entry (30.948979ms) +✔ an unknown business and a broken system config refuse with exit 3 (60.519247ms) +✔ empty views say so (0.635997ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.793789ms) +✔ tasks print the tracker fields the snapshot carries (0.123053ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (894.826658ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (210.283579ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (107.310036ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1114.791996ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (246.396166ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (123.225949ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (159.599231ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (148.908413ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (101.621072ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (147.699614ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (98.495281ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (195.909081ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.563362ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.962668ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.807648ms) +✔ bus start refuses with exit 3 without a notifier config (86.931375ms) +✔ bus start runs until bus stop; status reports it while it runs (656.245854ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.440125ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1269.168951ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (582.127299ms) +✔ a runner that stops at once ends its launch with the runner's reason (194.668315ms) +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (411.313142ms) +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3371.831319ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (103.943446ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (123.550258ms) +✔ a launch client that never closes its side doesn't hold the host's close (116.110583ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1221.909949ms) +✔ zoned uses the IANA zone across DST (15.464346ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (121.322639ms) +✔ two blocking decisions get two DMs with different nonces (109.849744ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.218512ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (91.817556ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (102.622632ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (104.131159ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (85.344784ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (154.050445ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (115.466925ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (116.380387ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (64.225797ms) +✔ an inbox read failure is logged and the next poll retries (0.625354ms) +✔ no Discord id reaches the journal or the log (59.787181ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (11.502098ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (16.81224ms) +✔ the journal: a whole file that is one torn line truncates to empty (10.277491ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.572598ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.569772ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.09614ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.32081ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.47107ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.37081ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.41303ms) +✔ digest content stays within Discord's 2000 characters (0.273349ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.7453ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (353.676147ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (35.615115ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2133.69872ms) +✔ busExit and refuseInsideAgent (0.424318ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (202.963437ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1126.390972ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (63.278309ms) +✔ launches off and on go to the broker and change the business's launch state (72.910608ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (75.227332ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 81 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7473.412895 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (411.313142ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 0 !== 1 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:292: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: 0, + expected: 1, + operator: 'strictEqual', + diff: 'simple' + } + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3371.831319ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 0 !== 1 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:292: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: 0, + expected: 1, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mo-policy-outside-try-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mo-policy-outside-try-harness.txt new file mode 100644 index 00000000..cb5498c4 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mo-policy-outside-try-harness.txt @@ -0,0 +1,91 @@ +✔ sessionModel: agent vars win, then the system's execution settings (9.696603ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.070273ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.415247ms) +✔ a bundle is written once: an existing file refuses (2.650431ms) +✔ a path with a single quote can't go into the hook command (1.629108ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (117.967314ms) +✔ a missing or wrong policy, or a bad event, exits 2 (88.522719ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1088.78013ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (727.356596ms) +✔ claude: the hook alone blocks a path outside the workspace (441.84677ms) +✔ claude: a second turn resumes the first turn's session (682.249827ms) +✔ claude adapter: --restricted is always passed (5.017916ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (734.030368ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.229436ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.088939ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.919719ms) +✔ file tool paths must resolve inside the workspace (1.440503ms) +✔ pi's own path normalisation can't be used to step out (1.352831ms) +✔ a symlink inside the workspace that points out is outside (1.127897ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.876064ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (13.39872ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.575674ms) +✔ claude path fields per tool (0.549012ms) +✔ glob patterns stay inside the workspace (0.629861ms) +✔ a path that can't be checked is blocked (0.329056ms) +✔ initialize, ping and tools/list (43.066025ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (31.509032ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (36.999754ms) +✔ a missing argument is a usage error (29.504074ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (358.687762ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (307.132959ms) +✔ pi: a read is refused when pi would open another spelling outside (308.486165ms) +✔ pi: a missing extension refuses before any model call (6.865577ms) +✔ pi: an extension without its configuration fails pi's start (250.574566ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.162396ms) +✔ turnRequest names the sender, class, reply and decision (0.181424ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (243.86309ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (110.32513ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (344.896033ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1295.707408ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (162.081379ms) +✔ founder credentials stop before the claim (20) (184.047182ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (199.883703ms) +✔ the launch ending under a running session exits 22 (144.133097ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (245.598286ms) +✔ a broker that is down at the claim exits 23, not 21 (90.217016ms) +✔ no capability, or a malformed one, on stdin exits 2 (198.136102ms) +✖ a missing or malformed policy exits 2 before the claim (99.52997ms) +✔ the PM gets launch, its task verbs and the reads (5.868932ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.718293ms) +✔ launch only when the business's launch block names the instance as launcher (1.482662ms) +✔ an action outside the instance's authority has no tool (1.66328ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (7.991204ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 52 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10258.988421 + +✖ failing tests: + +test at packages/harness/tests/runner.test.mjs:325:1 +✖ a missing or malformed policy exits 2 before the claim (99.52997ms) + AssertionError [ERR_ASSERTION]: :1 + { + + SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2) + at JSON.parse () + at main (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/src/runner.mjs:268:17) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/src/runner.mjs:369:22 + + Node.js v26.8.1 + + + 1 !== 2 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/runner.test.mjs:330: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: 1, + expected: 2, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mp-any-reply-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mp-any-reply-cli.txt new file mode 100644 index 00000000..f5d42440 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mp-any-reply-cli.txt @@ -0,0 +1,111 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (118.7696ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (112.517582ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (104.734582ms) +✔ decide prints a declining choice as declining (94.219447ms) +✔ an unknown outcome is reported once and never resent (71.320898ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (78.60463ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (89.27756ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (47.489336ms) +✔ every human command refuses inside an agent run before it touches the bus (71.874952ms) +✔ usage errors exit 4; no business and no host is a usage error (80.886603ms) +✔ agents and tasks print through the broker (101.461558ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.300893ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (41.15791ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (35.204711ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (31.261236ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (32.584002ms) +✔ a business without tracker.baseUrl gets no trackers entry (29.044532ms) +✔ an unknown business and a broken system config refuse with exit 3 (57.516076ms) +✔ empty views say so (0.957838ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.992012ms) +✔ tasks print the tracker fields the snapshot carries (0.170132ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (896.29727ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (196.663348ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (101.419879ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1115.868122ms) +✖ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (116.00259ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (119.922419ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (166.609267ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (171.368584ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (116.325005ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (147.739729ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (104.321743ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (160.96238ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.725385ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.213528ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (216.613384ms) +✔ bus start refuses with exit 3 without a notifier config (88.972158ms) +✔ bus start runs until bus stop; status reports it while it runs (657.485418ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.640535ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1269.229386ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (597.546968ms) +✔ a runner that stops at once ends its launch with the runner's reason (190.584908ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (655.747301ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3591.709876ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (110.022099ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (116.680949ms) +✔ a launch client that never closes its side doesn't hold the host's close (124.74906ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1234.875621ms) +✔ zoned uses the IANA zone across DST (17.445426ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (110.15799ms) +✔ two blocking decisions get two DMs with different nonces (120.4423ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.169566ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (97.392298ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (93.827284ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (84.938859ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (89.579454ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (136.189279ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (104.363841ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (134.265521ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (64.373716ms) +✔ an inbox read failure is logged and the next poll retries (0.604049ms) +✔ no Discord id reaches the journal or the log (46.22533ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (20.050399ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (13.683308ms) +✔ the journal: a whole file that is one torn line truncates to empty (12.83548ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.58997ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.553963ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.852277ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.316039ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.624091ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.43916ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.436619ms) +✔ digest content stays within Discord's 2000 characters (0.275316ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.09786ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (357.419838ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (33.15468ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2140.168774ms) +✔ busExit and refuseInsideAgent (0.425387ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (201.263318ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1128.405191ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (71.625526ms) +✔ launches off and on go to the broker and change the business's launch state (100.734888ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (78.664127ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 7970.621241 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:240:1 +✖ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (116.00259ms) + AssertionError [ERR_ASSERTION]: another id + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/host.test.mjs:258: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: CliError: identity refused: unauthenticated + at file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:205:29 + at process.processTicksAndRejections (node:internal/process/task_queues:104:5), + operator: 'rejects', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mq-no-timer-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mq-no-timer-cli.txt new file mode 100644 index 00000000..e30f2c55 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mq-no-timer-cli.txt @@ -0,0 +1,99 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (117.844202ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (112.051783ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (99.516979ms) +✔ decide prints a declining choice as declining (91.520575ms) +✔ an unknown outcome is reported once and never resent (75.283805ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (94.649371ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (50.873695ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (54.390982ms) +✔ every human command refuses inside an agent run before it touches the bus (72.259359ms) +✔ usage errors exit 4; no business and no host is a usage error (69.265454ms) +✔ agents and tasks print through the broker (76.72627ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.18508ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (45.671901ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (40.208247ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (40.892318ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (36.439434ms) +✔ a business without tracker.baseUrl gets no trackers entry (33.651213ms) +✔ an unknown business and a broken system config refuse with exit 3 (63.975733ms) +✔ empty views say so (1.151707ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.358693ms) +✔ tasks print the tracker fields the snapshot carries (0.204733ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (892.761758ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (204.03585ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (117.342796ms) +✖ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (30008.753719ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (231.390625ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (120.303616ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (154.309413ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (154.119163ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (101.399722ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (150.416295ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (117.674787ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (163.238776ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (26.14268ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.629888ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (207.528952ms) +✔ bus start refuses with exit 3 without a notifier config (87.103098ms) +✔ bus start runs until bus stop; status reports it while it runs (663.245212ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.054298ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1257.071227ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (587.596688ms) +✔ a runner that stops at once ends its launch with the runner's reason (187.544714ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (648.454694ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3561.089392ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (110.722706ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (148.641787ms) +✔ a launch client that never closes its side doesn't hold the host's close (123.135809ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1228.905979ms) +✔ zoned uses the IANA zone across DST (16.233393ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (119.044583ms) +✔ two blocking decisions get two DMs with different nonces (105.540828ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.205326ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (101.136924ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (95.877067ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (75.987785ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (90.920672ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (116.676169ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (112.950101ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (94.390305ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (53.908882ms) +✔ an inbox read failure is logged and the next poll retries (0.63442ms) +✔ no Discord id reaches the journal or the log (72.084211ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (10.343166ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (15.17722ms) +✔ the journal: a whole file that is one torn line truncates to empty (9.904163ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.56188ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.534631ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.723957ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.292499ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.437122ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.348808ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.41723ms) +✔ digest content stays within Discord's 2000 characters (0.235635ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.991265ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (370.153969ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (39.132227ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2163.396006ms) +✔ busExit and refuseInsideAgent (0.395561ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (191.049842ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1129.445914ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (75.113246ms) +✔ launches off and on go to the broker and change the business's launch state (73.878699ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (66.415468ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 0 +ℹ cancelled 1 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 33734.324949 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:206:1 +✖ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (30008.753719ms) + 'test timed out after 30000ms' diff --git a/agents/darkwing/work/s6-review/r3/mut/Mr-ignore-unsolicited-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mr-ignore-unsolicited-cli.txt new file mode 100644 index 00000000..81e4f423 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mr-ignore-unsolicited-cli.txt @@ -0,0 +1,114 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (116.271488ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (124.069222ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (104.243829ms) +✔ decide prints a declining choice as declining (97.196386ms) +✔ an unknown outcome is reported once and never resent (79.174498ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (88.606533ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (87.768697ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (47.324214ms) +✔ every human command refuses inside an agent run before it touches the bus (64.01629ms) +✔ usage errors exit 4; no business and no host is a usage error (60.613922ms) +✔ agents and tasks print through the broker (89.36753ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.407524ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (54.927595ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (40.01449ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (36.208705ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (35.601892ms) +✔ a business without tracker.baseUrl gets no trackers entry (34.864903ms) +✔ an unknown business and a broken system config refuse with exit 3 (68.275677ms) +✔ empty views say so (1.01893ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.571284ms) +✔ tasks print the tracker fields the snapshot carries (0.274696ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (918.699061ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (191.902414ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (117.63791ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1156.488334ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (235.475124ms) +✖ a broker reply with no request waiting breaks the channel and the host exits 1 (5114.342057ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (170.136813ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (152.483597ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (103.739555ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (160.130914ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (107.057276ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (162.775445ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.15224ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.679021ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (300.192263ms) +✔ bus start refuses with exit 3 without a notifier config (118.208902ms) +✔ bus start runs until bus stop; status reports it while it runs (723.480686ms) +✔ bus-service.sh renders the unit and installs it into a given directory (33.590569ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1268.563017ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (605.423586ms) +✔ a runner that stops at once ends its launch with the runner's reason (186.260867ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (638.409248ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3565.207706ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (109.678021ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (113.427619ms) +✔ a launch client that never closes its side doesn't hold the host's close (124.913124ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1224.938589ms) +✔ zoned uses the IANA zone across DST (18.314057ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (118.717327ms) +✔ two blocking decisions get two DMs with different nonces (110.677761ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.300255ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (92.543944ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (95.966315ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (88.423173ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (90.76417ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (156.662576ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (100.982989ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (103.052373ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (59.556795ms) +✔ an inbox read failure is logged and the next poll retries (0.580257ms) +✔ no Discord id reaches the journal or the log (72.423056ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (10.998211ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (21.426466ms) +✔ the journal: a whole file that is one torn line truncates to empty (19.539524ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.666391ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.640771ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.802934ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.299762ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.471224ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.348548ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.413823ms) +✔ digest content stays within Discord's 2000 characters (0.255603ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.982927ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (375.284603ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (42.802247ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2170.769467ms) +✔ busExit and refuseInsideAgent (0.403611ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (201.152444ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1129.541481ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (78.064968ms) +✔ launches off and on go to the broker and change the business's launch state (71.657225ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (68.686108ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10130.129185 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:265:1 +✖ a broker reply with no request waiting breaks the channel and the host exits 1 (5114.342057ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + actual - expected + + + 'still running' + - 1 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/host.test.mjs:283: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: 'still running', + expected: 1, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Ms-follow-walk-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Ms-follow-walk-harness.txt new file mode 100644 index 00000000..58cc6a57 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Ms-follow-walk-harness.txt @@ -0,0 +1,125 @@ +✔ sessionModel: agent vars win, then the system's execution settings (18.802442ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (8.290629ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (4.265218ms) +✔ a bundle is written once: an existing file refuses (4.837663ms) +✔ a path with a single quote can't go into the hook command (2.936744ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (173.049276ms) +✔ a missing or wrong policy, or a bad event, exits 2 (123.911977ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1128.878137ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (994.036261ms) +✔ claude: the hook alone blocks a path outside the workspace (557.302735ms) +✔ claude: a second turn resumes the first turn's session (832.823388ms) +✔ claude adapter: --restricted is always passed (5.786433ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (859.822688ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.864382ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.576156ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.113849ms) +✔ file tool paths must resolve inside the workspace (2.677582ms) +✔ pi's own path normalisation can't be used to step out (1.510552ms) +✔ a symlink inside the workspace that points out is outside (1.41079ms) +✖ a dangling symlink is refused at any depth, in both harnesses (2.226421ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (26.104867ms) +✖ other spellings cover directories, dangling links and pi's cwd (2.137358ms) +✔ claude path fields per tool (1.05581ms) +✔ glob patterns stay inside the workspace (1.137006ms) +✔ a path that can't be checked is blocked (0.84172ms) +✔ initialize, ping and tools/list (69.514094ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (49.843343ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (39.762264ms) +✔ a missing argument is a usage error (37.288971ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (495.019496ms) +✖ pi: a write through a dangling symlink is blocked, and nothing appears outside (422.140334ms) +✔ pi: a read is refused when pi would open another spelling outside (433.998971ms) +✔ pi: a missing extension refuses before any model call (8.617714ms) +✔ pi: an extension without its configuration fails pi's start (326.295407ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.908048ms) +✔ turnRequest names the sender, class, reply and decision (0.300329ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (285.748828ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (113.620011ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (433.85822ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1314.045957ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (150.182168ms) +✔ founder credentials stop before the claim (20) (188.63084ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (238.87844ms) +✔ the launch ending under a running session exits 22 (152.787017ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (253.577522ms) +✔ a broker that is down at the claim exits 23, not 21 (100.818778ms) +✔ no capability, or a malformed one, on stdin exits 2 (220.636759ms) +✔ a missing or malformed policy exits 2 before the claim (135.30631ms) +✔ the PM gets launch, its task verbs and the reads (9.883022ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.967614ms) +✔ launch only when the business's launch block names the instance as launcher (3.548277ms) +✔ an action outside the instance's authority has no tool (2.842622ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (13.441611ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 50 +ℹ fail 3 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10443.473356 + +✖ failing tests: + +test at packages/harness/tests/gate.test.mjs:82:1 +✖ a dangling symlink is refused at any depth, in both harnesses (2.226421ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at blocked (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:19:10) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:92:7) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } + +test at packages/harness/tests/gate.test.mjs:137:1 +✖ other spellings cover directories, dangling links and pi's cwd (2.137358ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + at blocked (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:19:10) + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/gate.test.mjs:146:3) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual', + diff: 'simple' + } + +test at packages/harness/tests/pi-session.test.mjs:99:1 +✖ pi: a write through a dangling symlink is blocked, and nothing appears outside (422.140334ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + false !== true + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/pi-session.test.mjs:112: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: false, + expected: true, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mt-no-socket-destroy-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mt-no-socket-destroy-cli.txt new file mode 100644 index 00000000..3d1a5605 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mt-no-socket-destroy-cli.txt @@ -0,0 +1,102 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (107.574979ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (115.510197ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (91.667832ms) +✔ decide prints a declining choice as declining (82.361002ms) +✔ an unknown outcome is reported once and never resent (88.689894ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (84.215775ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (81.428409ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (47.834263ms) +✔ every human command refuses inside an agent run before it touches the bus (73.603116ms) +✔ usage errors exit 4; no business and no host is a usage error (66.204753ms) +✔ agents and tasks print through the broker (77.16719ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.178487ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (50.972913ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (36.204781ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (33.055013ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (31.040273ms) +✔ a business without tracker.baseUrl gets no trackers entry (32.719494ms) +✔ an unknown business and a broken system config refuse with exit 3 (68.293181ms) +✔ empty views say so (0.882453ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.508547ms) +✔ tasks print the tracker fields the snapshot carries (0.253034ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (894.631322ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (194.887309ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (114.509565ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1121.999761ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (240.415817ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (113.972436ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (192.657947ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (169.644507ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (108.271037ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (157.044508ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (103.068297ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (166.142799ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.908346ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.408938ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (219.813334ms) +✔ bus start refuses with exit 3 without a notifier config (85.061294ms) +✔ bus start runs until bus stop; status reports it while it runs (689.34616ms) +✔ bus-service.sh renders the unit and installs it into a given directory (30.910722ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1270.671704ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (589.217311ms) +✔ a runner that stops at once ends its launch with the runner's reason (192.873676ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (681.213898ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3607.470054ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (136.069657ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (133.759484ms) +✖ a launch client that never closes its side doesn't hold the host's close (5150.040447ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1243.827657ms) +✔ zoned uses the IANA zone across DST (21.377114ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (104.828887ms) +✔ two blocking decisions get two DMs with different nonces (112.202921ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.344568ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (86.639654ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (87.580349ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (87.597711ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (80.082908ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (139.586274ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (116.848411ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (102.328643ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (50.901873ms) +✔ an inbox read failure is logged and the next poll retries (0.645418ms) +✔ no Discord id reaches the journal or the log (67.371584ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (11.095924ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (15.767322ms) +✔ the journal: a whole file that is one torn line truncates to empty (10.491987ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.624542ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.630051ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.18267ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.346859ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.518325ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.414284ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.471122ms) +✔ digest content stays within Discord's 2000 characters (0.301388ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.98474ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (379.207677ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (46.083511ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2163.000133ms) +✔ busExit and refuseInsideAgent (0.392922ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (190.534289ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1113.481004ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (75.811714ms) +✔ launches off and on go to the broker and change the business's launch state (76.441605ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (59.247583ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 82 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 13108.501852 + +✖ failing tests: + +test at packages/cli/tests/launcher.test.mjs:337:1 +✖ a launch client that never closes its side doesn't hold the host's close (5150.040447ms) + Error: close took over 5000 ms + at Timeout._onTimeout (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/tests/launcher.test.mjs:324:101) + at listOnTimeout (node:internal/timers:685:17) + at process.processTimers (node:internal/timers:618:7) diff --git a/agents/darkwing/work/s6-review/r3/mut/Mu-no-restricted-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mu-no-restricted-harness.txt new file mode 100644 index 00000000..6db93df8 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mu-no-restricted-harness.txt @@ -0,0 +1,105 @@ +✔ sessionModel: agent vars win, then the system's execution settings (14.572512ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (6.174951ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (4.600206ms) +✔ a bundle is written once: an existing file refuses (3.074615ms) +✔ a path with a single quote can't go into the hook command (2.569843ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (174.908763ms) +✔ a missing or wrong policy, or a bad event, exits 2 (103.705751ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1110.897092ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (979.613205ms) +✔ claude: the hook alone blocks a path outside the workspace (566.447612ms) +✔ claude: a second turn resumes the first turn's session (772.777865ms) +✖ claude adapter: --restricted is always passed (5.834159ms) +✖ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (419.9886ms) +✔ claude: a missing hook or MCP file refuses before claude starts (8.321942ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.740971ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.104453ms) +✔ file tool paths must resolve inside the workspace (1.531795ms) +✔ pi's own path normalisation can't be used to step out (1.36492ms) +✔ a symlink inside the workspace that points out is outside (1.197825ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.833957ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (20.828356ms) +✔ other spellings cover directories, dangling links and pi's cwd (3.062402ms) +✔ claude path fields per tool (0.980935ms) +✔ glob patterns stay inside the workspace (1.034332ms) +✔ a path that can't be checked is blocked (0.50853ms) +✔ initialize, ping and tools/list (71.834384ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (40.220197ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (41.041288ms) +✔ a missing argument is a usage error (35.408963ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (451.856779ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (424.083966ms) +✔ pi: a read is refused when pi would open another spelling outside (391.039296ms) +✔ pi: a missing extension refuses before any model call (8.084161ms) +✔ pi: an extension without its configuration fails pi's start (309.06089ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.760127ms) +✔ turnRequest names the sender, class, reply and decision (0.295283ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (289.066856ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (145.925972ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (572.943605ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1382.676577ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (195.488321ms) +✔ founder credentials stop before the claim (20) (266.220648ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (300.197563ms) +✔ the launch ending under a running session exits 22 (189.321104ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (294.356597ms) +✔ a broker that is down at the claim exits 23, not 21 (126.069492ms) +✔ no capability, or a malformed one, on stdin exits 2 (225.894325ms) +✔ a missing or malformed policy exits 2 before the claim (172.34215ms) +✔ the PM gets launch, its task verbs and the reads (9.918179ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.000201ms) +✔ launch only when the business's launch block names the instance as launcher (2.299358ms) +✔ an action outside the instance's authority has no tool (2.45242ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (12.67808ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 51 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10736.672106 + +✖ failing tests: + +test at packages/harness/tests/claude-session.test.mjs:143:1 +✖ claude adapter: --restricted is always passed (5.834159ms) + AssertionError [ERR_ASSERTION]: -p x --output-format text --system-prompt {} --model claude-sonnet-5-5 --tools --allowedTools mcp__mosaic --permission-mode dontAsk --settings /home/jwoltje/darkwing-scratch/tmp/mosaic-harness-W55fmH/settings.json --strict-mcp-config --mcp-config /home/jwoltje/darkwing-scratch/tmp/mosaic-harness-W55fmH/mcp.json --disable-slash-commands --session-id 884e6813-b3f5-4c85-9d05-e7f40f63932e + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/claude-session.test.mjs:166:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at packages/harness/tests/claude-session.test.mjs:170:1 +✖ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (419.9886ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + + [ + + 'MARKER-USER', + + 'MARKER-PARENT', + + 'MARKER-WS', + + 'MARKER-MEMORY' + + ] + - [] + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/harness/tests/claude-session.test.mjs:193: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: [ 'MARKER-USER', 'MARKER-PARENT', 'MARKER-WS', 'MARKER-MEMORY' ], + expected: [], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-bus.txt b/agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-bus.txt new file mode 100644 index 00000000..b9045ef4 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-bus.txt @@ -0,0 +1,102 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (186.289558ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (304.018727ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (312.896779ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (176.026053ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (214.016058ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (156.008229ms) +✔ task action subjects and linked decision trail are complete and ordered (186.987338ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (116.187895ms) +✔ business isolation includes inherited object names and cross-business message references (246.496569ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (251.859443ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (122.105337ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (218.667672ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (164.00194ms) +✔ both arbiters require human resolution when their cross-role route is themselves (257.710544ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.954557ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.251571ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.375131ms) +✔ expiry refuses use and env references never become client data (0.751766ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.531434ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.414561ms) +✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (154.694325ms) +✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (171.902185ms) +✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (174.905205ms) +✔ endLaunch leaves a claim another run took alone (212.217176ms) +✔ refuse records action.refused against the caller with the code only (138.391095ms) +✔ launches off refuses role.launch with launch-revoked until launches on (215.338187ms) +✖ broker process: launch ops authorize role.launch, record refusals and end runs (213.614803ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (3.386446ms) +✔ process reader gets own kernel identity without exposing environment values (0.689787ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.079005ms) +✔ real pid 1 remains inspectable when its environment is protected (0.397066ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (254.253894ms) +✔ missing and foreign-business citations refuse and roll back message and grant (212.627037ms) +✔ cross-role sends still need a matching resolved decision and consume it once (289.310244ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (218.517678ms) +✔ startup token refusal returns safe code without value or partial listening broker (32.491119ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (178.304823ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (191.566072ms) +✔ trusted host registers later launches; socket clients never have a registration verb (189.349722ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (36.89722ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (175.689007ms) +✔ v3b prototype refusals, views and append-only mutations (1197.281905ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (305.159404ms) +✔ another run cannot consume an approval; a failed check leaves it usable (262.492222ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (181.830806ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (346.4383ms) +✔ class drift gated to cross-role refuses before consumption (222.934014ms) +✔ class drift cross-role to gated refuses before consumption (241.225922ms) +✔ class drift gated to within-role refuses before consumption (251.437257ms) +✔ class drift cross-role to within-role refuses before consumption (223.715857ms) +✔ class drift within-role to gated refuses before consumption (178.191594ms) +✔ class drift within-role to cross-role refuses before consumption (157.94191ms) +✔ message.send consumes approval and prevents a later send or authorize (245.832812ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (292.283314ms) +✔ creates private WAL store and excludes a second writer until explicit close (140.014706ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (212.834441ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (219.258383ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (187.366748ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (117.888079ms) +✔ async transactions refuse before invoking their function (89.581295ms) +✔ recordTask keeps sync reads and a role write apart (215.864702ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (138.896899ms) +✔ a bad entry refuses the whole record (119.620932ms) +✔ taskView reads the projection for one business (146.650022ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (263.17009ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (411.847863ms) +✔ without an adapter the server refuses every task verb (196.381669ms) +✔ the runtime refuses an invalid adapter and closes a valid one (227.028171ms) +✔ the process loads the S3 adapter from plain-data trackers (291.300727ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (175.428009ms) +✔ two wire claims serialize; a lost reply never automatically retries (203.728929ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (124.215141ms) +✔ client preserves UTF-8 when a response divides a multibyte character (11.588695ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (170.147838ms) +ℹ tests 74 +ℹ suites 0 +ℹ pass 73 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3003.137811 + +✖ failing tests: + +test at packages/bus/tests/end-launch.test.mjs:135:1 +✖ broker process: launch ops authorize role.launch, record refusals and end runs (213.614803ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + undefined !== 7 + + at TestContext. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/bus/tests/end-launch.test.mjs:179: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: undefined, + expected: 7, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-cli.txt b/agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-cli.txt new file mode 100644 index 00000000..bb866f87 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mv-no-tag-cli.txt @@ -0,0 +1,278 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (131.259713ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (142.085261ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (105.183099ms) +✔ decide prints a declining choice as declining (119.576185ms) +✔ an unknown outcome is reported once and never resent (108.271628ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (117.946118ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (105.265348ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (87.364293ms) +✔ every human command refuses inside an agent run before it touches the bus (92.554678ms) +✔ usage errors exit 4; no business and no host is a usage error (95.690907ms) +✔ agents and tasks print through the broker (106.418073ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.152934ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (44.878382ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (33.233402ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (43.460218ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (32.215951ms) +✔ a business without tracker.baseUrl gets no trackers entry (33.560654ms) +✔ an unknown business and a broken system config refuse with exit 3 (57.823466ms) +✔ empty views say so (0.84178ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.86245ms) +✔ tasks print the tracker fields the snapshot carries (0.14085ms) +mosaic-notify: notify: poll failed: ENOENT: no such file or directory, open '/home/jwoltje/darkwing-scratch/tmp/mosaic-cli-RUIsFP/data/notify/acme/sent.jsonl' +✖ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (281.600404ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (238.665806ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (157.129686ms) +✖ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (165.459948ms) +✖ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (150.458949ms) +✖ a broker reply with no request waiting breaks the channel and the host exits 1 (174.768425ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (215.873785ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (190.646713ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (139.740017ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (174.760894ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (131.745328ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (192.164991ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.803458ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.070147ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (237.099028ms) +✔ bus start refuses with exit 3 without a notifier config (96.586437ms) +✔ bus start runs until bus stop; status reports it while it runs (662.387058ms) +✔ bus-service.sh renders the unit and installs it into a given directory (31.140179ms) +✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (207.214021ms) +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (158.789496ms) +✖ a runner that stops at once ends its launch with the runner's reason (163.795055ms) +file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166 + const fail = (code, text) => Object.assign(new CliError(text, 1), { code }); + ^ + +CliError: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' +} + +Node.js v26.8.1 +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (30002.536805ms) +file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166 + const fail = (code, text) => Object.assign(new CliError(text, 1), { code }); + ^ + +CliError: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' +} + +Node.js v26.8.1 +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (30030.798149ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (108.303111ms) +✖ an over-long launch request is refused at once, not at the 10 s idle timeout (101.198936ms) +✖ a launch client that never closes its side doesn't hold the host's close (104.110192ms) +✖ a runner that ignores SIGTERM is killed when the host closes (114.572483ms) +✔ zoned uses the IANA zone across DST (18.917292ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (134.68347ms) +✔ two blocking decisions get two DMs with different nonces (129.243829ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.198111ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (108.36552ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (111.438702ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (114.682617ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (119.110607ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (163.311187ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (123.358299ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (115.878308ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (95.469743ms) +✔ an inbox read failure is logged and the next poll retries (0.651764ms) +✔ no Discord id reaches the journal or the log (85.75436ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (32.250874ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (22.410351ms) +✔ the journal: a whole file that is one torn line truncates to empty (10.338875ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.598787ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.583001ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.764342ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.348821ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.596242ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.385734ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.435339ms) +✔ digest content stays within Discord's 2000 characters (0.274173ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.10812ms) +tasks acme startup tracker-unavailable +✖ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (254.292384ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (38.221259ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2161.424367ms) +✔ busExit and refuseInsideAgent (0.42253ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (223.258164ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1128.520386ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (61.74262ms) +✔ launches off and on go to the broker and change the business's launch state (110.05542ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (79.707587ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 70 +ℹ fail 11 +ℹ cancelled 2 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 61071.033649 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:124:1 +✖ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (281.600404ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/host.test.mjs:206:1 +✖ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (165.459948ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/host.test.mjs:240:1 +✖ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (150.458949ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/host.test.mjs:265:1 +✖ a broker reply with no request waiting breaks the channel and the host exits 1 (174.768425ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/launcher.test.mjs:106:1 +✖ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (207.214021ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/launcher.test.mjs:176:1 +✖ the PM launches a coder through its launch tool; the coder answers; refusals name their code (158.789496ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/launcher.test.mjs:237:1 +✖ a runner that stops at once ends its launch with the runner's reason (163.795055ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (30002.536805ms) + 'test timed out after 30000ms' + +test at packages/cli/tests/launcher.test.mjs:253:3 +✖ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (30030.798149ms) + 'test timed out after 30000ms' + +test at packages/cli/tests/launcher.test.mjs:326:1 +✖ an over-long launch request is refused at once, not at the 10 s idle timeout (101.198936ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/launcher.test.mjs:337:1 +✖ a launch client that never closes its side doesn't hold the host's close (104.110192ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/launcher.test.mjs:369:1 +✖ a runner that ignores SIGTERM is killed when the host closes (114.572483ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } + +test at packages/cli/tests/trackers-boot.test.mjs:15:1 +✖ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (254.292384ms) + Error [CliError]: broker channel broken: reply for another request + at fail (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:166:46) + at breakChannel (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:172:22) + at ChildProcess. (file:///home/jwoltje/darkwing-scratch/r41c/wt/packages/cli/src/host.mjs:185:12) + at ChildProcess.emit (node:events:514:20) + at emit (node:internal/child_process:973:14) + at process.processTicksAndRejections (node:internal/process/task_queues:91:21) { + exitCode: 1, + code: 'broker-channel-broken' + } diff --git a/agents/darkwing/work/s6-review/r3/mut/Mw-ampm-case-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mw-ampm-case-harness.txt new file mode 100644 index 00000000..7dc7d1da --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mw-ampm-case-harness.txt @@ -0,0 +1,61 @@ +✔ sessionModel: agent vars win, then the system's execution settings (10.246343ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (3.951755ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.402535ms) +✔ a bundle is written once: an existing file refuses (1.934816ms) +✔ a path with a single quote can't go into the hook command (2.262614ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (111.091382ms) +✔ a missing or wrong policy, or a bad event, exits 2 (74.793876ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1085.327641ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (717.967982ms) +✔ claude: the hook alone blocks a path outside the workspace (443.025053ms) +✔ claude: a second turn resumes the first turn's session (695.566229ms) +✔ claude adapter: --restricted is always passed (5.008005ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (742.36088ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.341431ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (2.755031ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.844161ms) +✔ file tool paths must resolve inside the workspace (1.281207ms) +✔ pi's own path normalisation can't be used to step out (0.910982ms) +✔ a symlink inside the workspace that points out is outside (0.713072ms) +✔ a dangling symlink is refused at any depth, in both harnesses (2.260126ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (15.58884ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.712946ms) +✔ claude path fields per tool (0.739662ms) +✔ glob patterns stay inside the workspace (0.649552ms) +✔ a path that can't be checked is blocked (0.337697ms) +✔ initialize, ping and tools/list (39.889139ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (31.495517ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (32.119051ms) +✔ a missing argument is a usage error (28.000457ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (349.928644ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (309.448556ms) +✔ pi: a read is refused when pi would open another spelling outside (320.575498ms) +✔ pi: a missing extension refuses before any model call (6.716815ms) +✔ pi: an extension without its configuration fails pi's start (250.946967ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.020641ms) +✔ turnRequest names the sender, class, reply and decision (0.176043ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (252.118653ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (102.288037ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (363.281615ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1304.389331ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (149.449049ms) +✔ founder credentials stop before the claim (20) (175.841808ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (194.916355ms) +✔ the launch ending under a running session exits 22 (148.831182ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (243.531138ms) +✔ a broker that is down at the claim exits 23, not 21 (94.670369ms) +✔ no capability, or a malformed one, on stdin exits 2 (185.171748ms) +✔ a missing or malformed policy exits 2 before the claim (126.341171ms) +✔ the PM gets launch, its task verbs and the reads (5.976061ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (1.657017ms) +✔ launch only when the business's launch block names the instance as launcher (1.401222ms) +✔ an action outside the instance's authority has no tool (1.3887ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (7.749218ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 53 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10274.723417 diff --git a/agents/darkwing/work/s6-review/r3/mut/Mx-curly-once-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mx-curly-once-harness.txt new file mode 100644 index 00000000..6103771b --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mx-curly-once-harness.txt @@ -0,0 +1,61 @@ +✔ sessionModel: agent vars win, then the system's execution settings (14.343425ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (5.638537ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (4.721407ms) +✔ a bundle is written once: an existing file refuses (3.071551ms) +✔ a path with a single quote can't go into the hook command (2.859383ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (127.67789ms) +✔ a missing or wrong policy, or a bad event, exits 2 (85.407757ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1094.833934ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (789.979886ms) +✔ claude: the hook alone blocks a path outside the workspace (511.725871ms) +✔ claude: a second turn resumes the first turn's session (761.824885ms) +✔ claude adapter: --restricted is always passed (6.79173ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (804.279094ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.573808ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.810604ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (1.0979ms) +✔ file tool paths must resolve inside the workspace (1.851106ms) +✔ pi's own path normalisation can't be used to step out (1.45015ms) +✔ a symlink inside the workspace that points out is outside (1.21636ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.460188ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (19.092284ms) +✔ other spellings cover directories, dangling links and pi's cwd (2.948261ms) +✔ claude path fields per tool (0.906414ms) +✔ glob patterns stay inside the workspace (0.937361ms) +✔ a path that can't be checked is blocked (0.425306ms) +✔ initialize, ping and tools/list (49.433065ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (34.272357ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (35.004252ms) +✔ a missing argument is a usage error (28.537658ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (396.249856ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (361.721059ms) +✔ pi: a read is refused when pi would open another spelling outside (383.040443ms) +✔ pi: a missing extension refuses before any model call (8.147024ms) +✔ pi: an extension without its configuration fails pi's start (291.156445ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.813443ms) +✔ turnRequest names the sender, class, reply and decision (0.285199ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (248.184269ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (117.786437ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (399.582458ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1340.303202ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (183.784854ms) +✔ founder credentials stop before the claim (20) (217.954933ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (197.257837ms) +✔ the launch ending under a running session exits 22 (142.358893ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (259.703652ms) +✔ a broker that is down at the claim exits 23, not 21 (82.791627ms) +✔ no capability, or a malformed one, on stdin exits 2 (187.745213ms) +✔ a missing or malformed policy exits 2 before the claim (135.068031ms) +✔ the PM gets launch, its task verbs and the reads (8.689469ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.328404ms) +✔ launch only when the business's launch block names the instance as launcher (2.915081ms) +✔ an action outside the instance's authority has no tool (1.95173ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (11.982309ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 53 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10429.882391 diff --git a/agents/darkwing/work/s6-review/r3/mut/My-lstat-error-skips-harness.txt b/agents/darkwing/work/s6-review/r3/mut/My-lstat-error-skips-harness.txt new file mode 100644 index 00000000..b2dcc139 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/My-lstat-error-skips-harness.txt @@ -0,0 +1,61 @@ +✔ sessionModel: agent vars win, then the system's execution settings (12.555195ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (6.170319ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.602008ms) +✔ a bundle is written once: an existing file refuses (2.523019ms) +✔ a path with a single quote can't go into the hook command (2.362886ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (115.462168ms) +✔ a missing or wrong policy, or a bad event, exits 2 (77.136936ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1083.557198ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (722.181402ms) +✔ claude: the hook alone blocks a path outside the workspace (437.74174ms) +✔ claude: a second turn resumes the first turn's session (703.59845ms) +✔ claude adapter: --restricted is always passed (5.054284ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (715.323704ms) +✔ claude: a missing hook or MCP file refuses before claude starts (10.669515ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (4.211013ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.977371ms) +✔ file tool paths must resolve inside the workspace (2.165975ms) +✔ pi's own path normalisation can't be used to step out (1.047375ms) +✔ a symlink inside the workspace that points out is outside (0.807179ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.000644ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (14.125719ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.641819ms) +✔ claude path fields per tool (0.591653ms) +✔ glob patterns stay inside the workspace (0.682848ms) +✔ a path that can't be checked is blocked (0.354795ms) +✔ initialize, ping and tools/list (44.624203ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (31.050211ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (31.822661ms) +✔ a missing argument is a usage error (28.123451ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (339.309843ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (317.613225ms) +✔ pi: a read is refused when pi would open another spelling outside (317.746599ms) +✔ pi: a missing extension refuses before any model call (7.622894ms) +✔ pi: an extension without its configuration fails pi's start (253.087683ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.192116ms) +✔ turnRequest names the sender, class, reply and decision (0.212611ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (251.237059ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (103.779843ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (372.237563ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1285.353563ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (152.95985ms) +✔ founder credentials stop before the claim (20) (181.193801ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (205.747556ms) +✔ the launch ending under a running session exits 22 (154.417096ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (243.556928ms) +✔ a broker that is down at the claim exits 23, not 21 (97.278746ms) +✔ no capability, or a malformed one, on stdin exits 2 (189.373795ms) +✔ a missing or malformed policy exits 2 before the claim (119.365017ms) +✔ the PM gets launch, its task verbs and the reads (6.794905ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.127387ms) +✔ launch only when the business's launch block names the instance as launcher (1.797105ms) +✔ an action outside the instance's authority has no tool (1.930041ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (8.200943ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 53 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10269.956866 diff --git a/agents/darkwing/work/s6-review/r3/mut/Mz-spell-no-normalise-harness.txt b/agents/darkwing/work/s6-review/r3/mut/Mz-spell-no-normalise-harness.txt new file mode 100644 index 00000000..d8560b35 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/Mz-spell-no-normalise-harness.txt @@ -0,0 +1,61 @@ +✔ sessionModel: agent vars win, then the system's execution settings (13.519198ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (5.424125ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.200913ms) +✔ a bundle is written once: an existing file refuses (3.004429ms) +✔ a path with a single quote can't go into the hook command (2.555366ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (129.648019ms) +✔ a missing or wrong policy, or a bad event, exits 2 (81.213782ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1089.079074ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (729.439366ms) +✔ claude: the hook alone blocks a path outside the workspace (451.150684ms) +✔ claude: a second turn resumes the first turn's session (787.860007ms) +✔ claude adapter: --restricted is always passed (5.150014ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (726.554277ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.485743ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (3.005067ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.66828ms) +✔ file tool paths must resolve inside the workspace (1.022193ms) +✔ pi's own path normalisation can't be used to step out (1.155852ms) +✔ a symlink inside the workspace that points out is outside (1.130253ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.345858ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (16.584438ms) +✔ other spellings cover directories, dangling links and pi's cwd (2.106428ms) +✔ claude path fields per tool (0.644329ms) +✔ glob patterns stay inside the workspace (0.889664ms) +✔ a path that can't be checked is blocked (0.413074ms) +✔ initialize, ping and tools/list (43.586567ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (29.478652ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (30.109949ms) +✔ a missing argument is a usage error (27.033625ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (350.420799ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (338.949577ms) +✔ pi: a read is refused when pi would open another spelling outside (314.221744ms) +✔ pi: a missing extension refuses before any model call (6.61959ms) +✔ pi: an extension without its configuration fails pi's start (258.129614ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.025453ms) +✔ turnRequest names the sender, class, reply and decision (0.157949ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (281.203906ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (96.136612ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (371.393724ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1290.621304ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (157.023919ms) +✔ founder credentials stop before the claim (20) (181.001525ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (234.389133ms) +✔ the launch ending under a running session exits 22 (150.048776ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (249.049508ms) +✔ a broker that is down at the claim exits 23, not 21 (103.779436ms) +✔ no capability, or a malformed one, on stdin exits 2 (191.082679ms) +✔ a missing or malformed policy exits 2 before the claim (128.299667ms) +✔ the PM gets launch, its task verbs and the reads (6.617572ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.273362ms) +✔ launch only when the business's launch block names the instance as launcher (1.795695ms) +✔ an action outside the instance's authority has no tool (1.458454ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (8.824237ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 53 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10303.840732 diff --git a/agents/darkwing/work/s6-review/r3/mut/all.sh b/agents/darkwing/work/s6-review/r3/mut/all.sh new file mode 100755 index 00000000..d6b87d9a --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/all.sh @@ -0,0 +1,33 @@ +#!/bin/bash +R=~/darkwing-scratch/r41c/mut +rm -f $R/summary.txt $R/M*-*.txt +$R/run.sh Ma-late-signals harness cli +$R/run.sh Mb-runs-set-early bus cli +$R/run.sh Mc-no-run-ended bus cli +$R/run.sh Md-no-instance-running cli +$R/run.sh Me-no-authorize cli +$R/run.sh Mf-no-exit2-wrapper harness +$R/run.sh Mg-no-founder-check harness cli +$R/run.sh Mh-no-close-sigkill cli +$R/run.sh Mi-recover-no-kill cli +$R/run.sh Mj-no-stdin-cap harness +$R/run.sh Mk-launch-line-max cli +$R/run.sh Ml-gate-pattern harness +$R/run.sh Mm-no-revoke bus cli +$R/run.sh Mn-recover-no-end cli +$R/run.sh Mo-policy-outside-try harness +$R/run.sh Mp-any-reply cli +$R/run.sh Mq-no-timer cli +$R/run.sh Mr-ignore-unsolicited cli +$R/run.sh Ms-follow-walk harness +$R/run.sh Mt-no-socket-destroy cli +$R/run.sh Mu-no-restricted harness +$R/run.sh Mv-no-tag bus cli +$R/run.sh Mw-ampm-case harness +$R/run.sh Mx-curly-once harness +$R/run.sh My-lstat-error-skips harness +$R/run.sh Mz-spell-no-normalise harness +$R/run.sh MA-pi-only harness +$R/run.sh MB-no-real-base harness +$R/run.sh MC-no-nfd-curly harness +echo DONE >> $R/summary.txt diff --git a/agents/darkwing/work/s6-review/r3/mut/manifest-after.txt b/agents/darkwing/work/s6-review/r3/mut/manifest-after.txt new file mode 100644 index 00000000..e0d5cd2a --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/manifest-after.txt @@ -0,0 +1,42 @@ +adapters/README.md: OK +adapters/claude/adapter.sh: OK +adapters/pi/adapter.sh: OK +docs/TOOLS.md: OK +packages/bus/README.md: OK +packages/bus/src/broker.mjs: OK +packages/bus/src/process.mjs: OK +packages/bus/src/runtime.mjs: OK +packages/bus/tests/end-launch.test.mjs: OK +packages/cli/README.md: OK +packages/cli/src/cli.mjs: OK +packages/cli/src/host.mjs: OK +packages/cli/src/launcher.mjs: OK +packages/cli/tests/fixtures/launch-host.mjs: OK +packages/cli/tests/host.test.mjs: OK +packages/cli/tests/launcher.test.mjs: OK +packages/cli/tests/verbs.test.mjs: OK +packages/harness/README.md: OK +packages/harness/package.json: OK +packages/harness/src/bundle.mjs: OK +packages/harness/src/claude-gate.mjs: OK +packages/harness/src/gate.mjs: OK +packages/harness/src/mcp-server.mjs: OK +packages/harness/src/pi-extension.mjs: OK +packages/harness/src/runner.mjs: OK +packages/harness/src/tools.mjs: OK +packages/harness/tests/bundle.test.mjs: OK +packages/harness/tests/claude-gate.test.mjs: OK +packages/harness/tests/claude-session.test.mjs: OK +packages/harness/tests/fixtures/fake-adapter.mjs: OK +packages/harness/tests/gate.test.mjs: OK +packages/harness/tests/helpers.mjs: OK +packages/harness/tests/mcp-server.test.mjs: OK +packages/harness/tests/pi-session.test.mjs: OK +packages/harness/tests/runner.test.mjs: OK +packages/harness/tests/tools.test.mjs: OK +packages/seat/README.md: OK +packages/seat/src/proc.mjs: OK +packages/seat/src/session.mjs: OK +packages/seat/tests/session.test.mjs: OK +scripts/agent-host-dev.sh: OK +scripts/mosaic: OK diff --git a/agents/darkwing/work/s6-review/r3/mut/mutate.py b/agents/darkwing/work/s6-review/r3/mut/mutate.py new file mode 100644 index 00000000..be713c12 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/mutate.py @@ -0,0 +1,73 @@ +# mutate.py : apply one named mutant (exact text, must match once). +import sys +M = { + "Ma-late-signals": ("packages/harness/src/runner.mjs", + ' process.on("SIGTERM", stop);\n process.on("SIGINT", stop);\n\n let session, cap, policy;', + ' let session, cap, policy;', ), + "Mb-runs-set-early": ("packages/bus/src/broker.mjs", + " const session = { ...record, address: record.address ?? null, human: false };\n", + " const session = { ...record, address: record.address ?? null, human: false };\n this.#runs.set(key, session);\n"), + "Mc-no-run-ended": ("packages/bus/src/broker.mjs", + " fail('run-ended');\n } else", " void 0;\n } else"), + "Md-no-instance-running": ("packages/cli/src/launcher.mjs", + ' if (registry.running(b.id, instance)) throw new Refusal("instance-running");\n', ""), + "Me-no-authorize": ("packages/cli/src/launcher.mjs", + ' await host.op({ op: "authorizeLaunch", cap, instance });\n', ""), + "Mf-no-exit2-wrapper": ("packages/harness/src/bundle.mjs", + "${quote(files.policy)} || exit 2`", "${quote(files.policy)}`"), + "Mg-no-founder-check": ("packages/harness/src/runner.mjs", + " const found = FOUNDER_ENV.filter((k) => env[k] !== undefined);", " const found = [];"), + "Mh-no-close-sigkill": ("packages/cli/src/launcher.mjs", + ' if (startTimeOf(s.pid) === s.startTime) process.kill(s.pid, "SIGKILL");', " void 0;"), + "Mi-recover-no-kill": ("packages/cli/src/launcher.mjs", + ' process.kill(s.pid, "SIGKILL");', " void 0;"), + "Mj-no-stdin-cap": ("packages/harness/src/runner.mjs", + " if (input.length > 4096) reject", " if (false) reject"), + "Mk-launch-line-max": ("packages/cli/src/launcher.mjs", + " if (buf.length > LINE_MAX) return reply", " if (false) return reply"), + "Ml-gate-pattern": ("packages/harness/src/gate.mjs", + "/(^|[/\\\\])\\.\\.([/\\\\]|$)/.test(pattern)", "false"), + "Mm-no-revoke": ("packages/bus/src/broker.mjs", + " fail('launch-revoked');", " void 0;"), + "Mn-recover-no-end": ("packages/cli/src/launcher.mjs", + ' await endRun(s.run, "host-lost", null);\n', ""), + "Mo-policy-outside-try": ("packages/harness/src/runner.mjs", + ' policy = JSON.parse(readFileSync(session.bundle.policy, "utf8"));\n } catch (e) {\n log(`runner: ${e.message}`);\n return EXIT.usage;\n }\n', + ' } catch (e) {\n log(`runner: ${e.message}`);\n return EXIT.usage;\n }\n policy = JSON.parse(readFileSync(session.bundle.policy, "utf8"));\n'), + "Mp-any-reply": ("packages/cli/src/host.mjs", + " if (pending && m?.id === pending.id) {", " if (pending) {"), + "Mq-no-timer": ("packages/cli/src/host.mjs", + " const timer = setTimeout(() => breakChannel(`no reply within ${Math.round(requestTimeoutMs / 1000)} s`), requestTimeoutMs);", + " const timer = null;"), + "Mr-ignore-unsolicited": ("packages/cli/src/host.mjs", + ' } else breakChannel(pending ? "reply for another request" : "reply with no request waiting");', + ' } else if (pending) breakChannel("reply for another request");'), + "Ms-follow-walk": ("packages/harness/src/gate.mjs", + " while (!lstatSync(head, { throwIfNoEntry: false })) {", + " while (!(() => { try { return realpathSync(head); } catch { return null; } })()) {"), + "Mt-no-socket-destroy": ("packages/cli/src/launcher.mjs", + " for (const socket of sockets) socket.destroy();", " void sockets;"), + "Mu-no-restricted": ("adapters/claude/adapter.sh", + " --restricted \\\n", ""), + "Mv-no-tag": ("packages/bus/src/process.mjs", + "const tag = (message, reply) => (Number.isSafeInteger(message?.id) ? { ...reply, id: message.id } : reply);", + "const tag = (message, reply) => reply;"), + # Round 3: the R4 spelling check. + "Mw-ampm-case": ("packages/harness/src/gate.mjs", "/ (AM|PM)\\./gi", "/ (AM|PM)\\./g"), + "Mx-curly-once": ("packages/harness/src/gate.mjs", "v.replace(/'/g, \"\\u2019\")", "v.replace(/'/, \"\\u2019\")"), + "My-lstat-error-skips": ("packages/harness/src/gate.mjs", + "if (error.code === \"ENOTDIR\") continue;\n return `${tool} path can't be checked under another spelling: ${error.code ?? error.message}`;", + "continue;"), + "Mz-spell-no-normalise": ("packages/harness/src/gate.mjs", "const s = normalise(p);\n const bases", "const s = p;\n const bases"), + "MA-pi-only": ("packages/harness/src/gate.mjs", "if (tool === (claude ? \"Read\" : \"read\")) {", "if (tool === \"read\") {"), + "MB-no-real-base": ("packages/harness/src/gate.mjs", + "[resolve(workspace, s), resolve(realpathSync(workspace), s)]", "[resolve(workspace, s)]"), + "MC-no-nfd-curly": ("packages/harness/src/gate.mjs", "curly(r), curly(nfd)]", "curly(r)]"), +} +f, old, new = M[sys.argv[1]] +if "--file-only" in sys.argv: print(f); sys.exit(0) +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/s6-review/r3/mut/parse.sh b/agents/darkwing/work/s6-review/r3/mut/parse.sh new file mode 100755 index 00000000..f296ad4b --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/parse.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# parse.sh: pass/fail and failing test names per mutant output (spec reporter). +cd ~/darkwing-scratch/r41c/mut +for f in M*-*.txt; do + p=$(grep -E '^ℹ pass' "$f" | awk '{print $3}'); x=$(grep -E '^ℹ fail' "$f" | awk '{print $3}') + echo "$f: pass $p fail $x" + [ "$x" != 0 ] && awk '/^✖ failing tests:/{on=1;next} on && /^✖ /{sub(/^✖ /," "); sub(/ \([0-9.]+ms\)$/,""); print}' "$f" | sort -u +done diff --git a/agents/darkwing/work/s6-review/r3/mut/parse.txt b/agents/darkwing/work/s6-review/r3/mut/parse.txt new file mode 100644 index 00000000..a4b0440d --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/parse.txt @@ -0,0 +1,87 @@ +Ma-late-signals-cli.txt: pass 79 fail 1 + a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again + a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again + bus start --pm: the PM runs under its own PID namespace, registered, with a manifest + the PM launches a coder through its launch tool; the coder answers; refusals name their code +Ma-late-signals-harness.txt: pass 50 fail 3 + a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 + a SIGTERM before the claim stops the runner with exit 0 and no claim + SIGTERM during a turn kills the turn's process group and still exits 0 +MA-pi-only-harness.txt: pass 52 fail 1 + read is checked under every spelling pi's read would open, in both harnesses +MB-no-real-base-harness.txt: pass 52 fail 1 + other spellings cover directories, dangling links and pi's cwd +Mb-runs-set-early-bus.txt: pass 73 fail 1 + a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound +Mb-runs-set-early-cli.txt: pass 83 fail 0 +MC-no-nfd-curly-harness.txt: pass 51 fail 2 + pi: a read is refused when pi would open another spelling outside + read is checked under every spelling pi's read would open, in both harnesses +Mc-no-run-ended-bus.txt: pass 73 fail 1 + a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound +Mc-no-run-ended-cli.txt: pass 81 fail 2 + a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again + a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again +Md-no-instance-running-cli.txt: pass 81 fail 2 + bus start --pm: the PM runs under its own PID namespace, registered, with a manifest + the PM launches a coder through its launch tool; the coder answers; refusals name their code +Me-no-authorize-cli.txt: pass 82 fail 1 + the PM launches a coder through its launch tool; the coder answers; refusals name their code +Mf-no-exit2-wrapper-harness.txt: pass 52 fail 1 + a claude-code bundle adds the wrapped gate hook and the MCP config +Mg-no-founder-check-cli.txt: pass 83 fail 0 +Mg-no-founder-check-harness.txt: pass 51 fail 2 + founderCheck: founder variables, then a needed service without a usable token + founder credentials stop before the claim (20) +Mh-no-close-sigkill-cli.txt: pass 82 fail 1 + a runner that ignores SIGTERM is killed when the host closes +Mi-recover-no-kill-cli.txt: pass 82 fail 1 + a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again +Mj-no-stdin-cap-harness.txt: pass 52 fail 1 + no capability, or a malformed one, on stdin exits 2 +Mk-launch-line-max-cli.txt: pass 82 fail 1 + an over-long launch request is refused at once, not at the 10 s idle timeout +Ml-gate-pattern-harness.txt: pass 52 fail 1 + glob patterns stay inside the workspace +Mm-no-revoke-bus.txt: pass 73 fail 1 + launches off refuses role.launch with launch-revoked until launches on +Mm-no-revoke-cli.txt: pass 83 fail 0 +Mn-recover-no-end-cli.txt: pass 81 fail 2 + a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again + a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again +Mo-policy-outside-try-harness.txt: pass 52 fail 1 + a missing or malformed policy exits 2 before the claim +Mp-any-reply-cli.txt: pass 82 fail 1 + a broker reply with another request's id, or none, breaks the channel and the host exits 1 +Mq-no-timer-cli.txt: pass 82 fail 0 +Mr-ignore-unsolicited-cli.txt: pass 82 fail 1 + a broker reply with no request waiting breaks the channel and the host exits 1 +Ms-follow-walk-harness.txt: pass 50 fail 3 + a dangling symlink is refused at any depth, in both harnesses + other spellings cover directories, dangling links and pi's cwd + pi: a write through a dangling symlink is blocked, and nothing appears outside +Mt-no-socket-destroy-cli.txt: pass 82 fail 1 + a launch client that never closes its side doesn't hold the host's close +Mu-no-restricted-harness.txt: pass 51 fail 2 + claude adapter: --restricted is always passed + claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do +Mv-no-tag-bus.txt: pass 73 fail 1 + broker process: launch ops authorize role.launch, record refusals and end runs +Mv-no-tag-cli.txt: pass 70 fail 11 + a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 + a broker reply with another request's id, or none, breaks the channel and the host exits 1 + a broker reply with no request waiting breaks the channel and the host exits 1 + a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again + a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again + a launch client that never closes its side doesn't hold the host's close + an over-long launch request is refused at once, not at the 10 s idle timeout + a runner that ignores SIGTERM is killed when the host closes + a runner that stops at once ends its launch with the runner's reason + bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja + bus start --pm: the PM runs under its own PID namespace, registered, with a manifest + the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once + the PM launches a coder through its launch tool; the coder answers; refusals name their code +Mw-ampm-case-harness.txt: pass 53 fail 0 +Mx-curly-once-harness.txt: pass 53 fail 0 +My-lstat-error-skips-harness.txt: pass 53 fail 0 +Mz-spell-no-normalise-harness.txt: pass 53 fail 0 diff --git a/agents/darkwing/work/s6-review/r3/mut/run.sh b/agents/darkwing/work/s6-review/r3/mut/run.sh new file mode 100755 index 00000000..515de840 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# run.sh ...: mutate, run each package's node suite, restore from a backup copy. +set -u +cd ~/darkwing-scratch/r41c/wt +export TMPDIR=~/darkwing-scratch/tmp DOCKER_HOST=unix:///nonexistent.sock +m=$1; shift +f=$(python3 ../mut/mutate.py "$m" --file-only) || { echo "$m: no file" | tee -a ../mut/summary.txt; exit 1; } +cp -p "$f" ../mut/backup.tmp +python3 ../mut/mutate.py "$m" > /dev/null || { echo "$m: no match" | tee -a ../mut/summary.txt; exit 1; } +line="$m ($f):" +for p in "$@"; do + out=../mut/$m-$p.txt + timeout 900 node --test "packages/$p/tests/*.test.mjs" > "$out" 2>&1 + rc=$? + pass=$(grep -E '^ℹ pass' "$out" | awk '{print $3}'); fail=$(grep -E '^ℹ fail' "$out" | awk '{print $3}') + line="$line $p rc $rc pass ${pass:-?} fail ${fail:-?};" +done +cp -p ../mut/backup.tmp "$f" && rm ../mut/backup.tmp +echo "$line" | tee -a ../mut/summary.txt diff --git a/agents/darkwing/work/s6-review/r3/mut/summary.txt b/agents/darkwing/work/s6-review/r3/mut/summary.txt new file mode 100644 index 00000000..29568399 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/summary.txt @@ -0,0 +1,30 @@ +Ma-late-signals (packages/harness/src/runner.mjs): harness rc 1 pass 50 fail 3; cli rc 1 pass 79 fail 1; +Mb-runs-set-early (packages/bus/src/broker.mjs): bus rc 1 pass 73 fail 1; cli rc 0 pass 83 fail 0; +Mc-no-run-ended (packages/bus/src/broker.mjs): bus rc 1 pass 73 fail 1; cli rc 1 pass 81 fail 2; +Md-no-instance-running (packages/cli/src/launcher.mjs): cli rc 1 pass 81 fail 2; +Me-no-authorize (packages/cli/src/launcher.mjs): cli rc 1 pass 82 fail 1; +Mf-no-exit2-wrapper (packages/harness/src/bundle.mjs): harness rc 1 pass 52 fail 1; +Mg-no-founder-check (packages/harness/src/runner.mjs): harness rc 1 pass 51 fail 2; cli rc 0 pass 83 fail 0; +Mh-no-close-sigkill (packages/cli/src/launcher.mjs): cli rc 1 pass 82 fail 1; +Mi-recover-no-kill (packages/cli/src/launcher.mjs): cli rc 1 pass 82 fail 1; +Mj-no-stdin-cap (packages/harness/src/runner.mjs): harness rc 1 pass 52 fail 1; +Mk-launch-line-max (packages/cli/src/launcher.mjs): cli rc 1 pass 82 fail 1; +Ml-gate-pattern (packages/harness/src/gate.mjs): harness rc 1 pass 52 fail 1; +Mm-no-revoke (packages/bus/src/broker.mjs): bus rc 1 pass 73 fail 1; cli rc 0 pass 83 fail 0; +Mn-recover-no-end (packages/cli/src/launcher.mjs): cli rc 1 pass 81 fail 2; +Mo-policy-outside-try (packages/harness/src/runner.mjs): harness rc 1 pass 52 fail 1; +Mp-any-reply (packages/cli/src/host.mjs): cli rc 1 pass 82 fail 1; +Mq-no-timer (packages/cli/src/host.mjs): cli rc 1 pass 82 fail 0; +Mr-ignore-unsolicited (packages/cli/src/host.mjs): cli rc 1 pass 82 fail 1; +Ms-follow-walk (packages/harness/src/gate.mjs): harness rc 1 pass 50 fail 3; +Mt-no-socket-destroy (packages/cli/src/launcher.mjs): cli rc 1 pass 82 fail 1; +Mu-no-restricted (adapters/claude/adapter.sh): harness rc 1 pass 51 fail 2; +Mv-no-tag (packages/bus/src/process.mjs): bus rc 1 pass 73 fail 1; cli rc 1 pass 70 fail 11; +Mw-ampm-case (packages/harness/src/gate.mjs): harness rc 0 pass 53 fail 0; +Mx-curly-once (packages/harness/src/gate.mjs): harness rc 0 pass 53 fail 0; +My-lstat-error-skips (packages/harness/src/gate.mjs): harness rc 0 pass 53 fail 0; +Mz-spell-no-normalise (packages/harness/src/gate.mjs): harness rc 0 pass 53 fail 0; +MA-pi-only (packages/harness/src/gate.mjs): harness rc 1 pass 52 fail 1; +MB-no-real-base (packages/harness/src/gate.mjs): harness rc 1 pass 52 fail 1; +MC-no-nfd-curly (packages/harness/src/gate.mjs): harness rc 1 pass 51 fail 2; +DONE diff --git a/agents/darkwing/work/s6-review/r3/mut/survivors.sh b/agents/darkwing/work/s6-review/r3/mut/survivors.sh new file mode 100755 index 00000000..e720d9cc --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/mut/survivors.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# survivors.sh: probe/spell-edges.mjs under each R4 mutant the suite misses, +# printing only the cases whose decision changed (BAD lines). +cd ~/darkwing-scratch/r41c/wt +export TMPDIR=~/darkwing-scratch/tmp +for m in Mw-ampm-case Mx-curly-once My-lstat-error-skips Mz-spell-no-normalise; do + f=$(python3 ../mut/mutate.py "$m" --file-only) + cp -p "$f" ../mut/backup.tmp + python3 ../mut/mutate.py "$m" > /dev/null + echo "== $m" + WT=$PWD node ../probe/spell-edges.mjs | grep '^BAD' || echo " no case changed" + cp -p ../mut/backup.tmp "$f" && rm ../mut/backup.tmp +done diff --git a/agents/darkwing/work/s6-review/r3/out/node-bus.txt b/agents/darkwing/work/s6-review/r3/out/node-bus.txt new file mode 100644 index 00000000..da37c525 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/node-bus.txt @@ -0,0 +1,82 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (220.957629ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (227.441268ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (256.477003ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (152.974929ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (153.019134ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (148.24436ms) +✔ task action subjects and linked decision trail are complete and ordered (134.557177ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (86.508128ms) +✔ business isolation includes inherited object names and cross-business message references (145.63958ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (209.966328ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (103.089921ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (179.260873ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (106.37895ms) +✔ both arbiters require human resolution when their cross-role route is themselves (175.276721ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.23085ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (5.510591ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.644612ms) +✔ expiry refuses use and env references never become client data (1.017741ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (2.409037ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.585379ms) +✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (190.502933ms) +✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (122.686404ms) +✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (131.085157ms) +✔ endLaunch leaves a claim another run took alone (167.025936ms) +✔ refuse records action.refused against the caller with the code only (128.483213ms) +✔ launches off refuses role.launch with launch-revoked until launches on (166.469245ms) +✔ broker process: launch ops authorize role.launch, record refusals and end runs (246.29235ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.214793ms) +✔ process reader gets own kernel identity without exposing environment values (1.485511ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.31949ms) +✔ real pid 1 remains inspectable when its environment is protected (0.399904ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (244.629581ms) +✔ missing and foreign-business citations refuse and roll back message and grant (164.388067ms) +✔ cross-role sends still need a matching resolved decision and consume it once (240.284956ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (200.539371ms) +✔ startup token refusal returns safe code without value or partial listening broker (37.144047ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (154.358827ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (147.005744ms) +✔ trusted host registers later launches; socket clients never have a registration verb (180.844124ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (37.498785ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (163.513745ms) +✔ v3b prototype refusals, views and append-only mutations (1032.775158ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (288.534795ms) +✔ another run cannot consume an approval; a failed check leaves it usable (203.213167ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (163.083798ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (287.356285ms) +✔ class drift gated to cross-role refuses before consumption (195.331539ms) +✔ class drift cross-role to gated refuses before consumption (173.765127ms) +✔ class drift gated to within-role refuses before consumption (152.22207ms) +✔ class drift cross-role to within-role refuses before consumption (173.73842ms) +✔ class drift within-role to gated refuses before consumption (150.684848ms) +✔ class drift within-role to cross-role refuses before consumption (139.457096ms) +✔ message.send consumes approval and prevents a later send or authorize (174.93086ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (189.975834ms) +✔ creates private WAL store and excludes a second writer until explicit close (177.263297ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (160.612044ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (168.487262ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (162.687402ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (96.912755ms) +✔ async transactions refuse before invoking their function (83.602723ms) +✔ recordTask keeps sync reads and a role write apart (210.413533ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (102.424219ms) +✔ a bad entry refuses the whole record (91.011323ms) +✔ taskView reads the projection for one business (125.015906ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (242.31508ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (387.647203ms) +✔ without an adapter the server refuses every task verb (176.993898ms) +✔ the runtime refuses an invalid adapter and closes a valid one (155.975359ms) +✔ the process loads the S3 adapter from plain-data trackers (222.273341ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (195.530481ms) +✔ two wire claims serialize; a lost reply never automatically retries (156.979483ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (105.724575ms) +✔ client preserves UTF-8 when a response divides a multibyte character (12.436632ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (141.636338ms) +ℹ tests 74 +ℹ suites 0 +ℹ pass 74 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2380.39928 diff --git a/agents/darkwing/work/s6-review/r3/out/node-business.txt b/agents/darkwing/work/s6-review/r3/out/node-business.txt new file mode 100644 index 00000000..8dda63b6 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/node-business.txt @@ -0,0 +1,68 @@ +✔ config directory and file path follow MOSAIC_CONFIG (1.24599ms) +✔ the fixture business validates and comes back frozen (3.477502ms) +✔ two instances may share a definition (1.074218ms) +✔ top-level refusals (3.353058ms) +✔ arbiters and projects (4.747791ms) +✔ role instances (2.45371ms) +✔ Vikunja bots (5.617531ms) +✔ a role without Vikunja takes no tracker block (1.915168ms) +✔ credential references match the definition's services (2.246049ms) +✔ launch (6.396058ms) +✔ loadBusiness: file checks (1.582897ms) +✔ loadBusiness: not a regular file (37.489832ms) +✔ loading writes nothing (1.21926ms) +✔ names that are Object.prototype properties don't count as declared (2.611388ms) +✔ the shipped example refuses as written and validates once filled in (0.527522ms) +✔ usage errors exit 4 (273.981222ms) +✔ validate: a good business exits 0 and prints instance digests (65.020757ms) +✔ validate: project files (317.996951ms) +✔ validate: missing files and a broken system config (238.31255ms) +✔ validate: credential reference problems exit 2 and name each one (64.603872ms) +✔ validate: a token file inside the repository is refused (63.835692ms) +✔ validate: role definitions come from MOSAIC_ROLES_DIR (193.82671ms) +✔ resolve: prints one instance's record (189.018649ms) +✔ resolve: refusals (377.972892ms) +✔ parse: exactly one of file or env, plus the service's date (2.240667ms) +✔ check: a good file has no problems (0.652014ms) +✔ check never opens the file: a write-only token passes (0.291079ms) +✔ check: file problems (0.74892ms) +✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.687946ms) +✔ check: dates and environment references (0.338234ms) +✔ path and load (1.87955ms) +✔ refusals (1.021785ms) +✔ systemVars flattens the validated config (1.633904ms) +✔ precedence: system, business, project, project role, agent (4.522979ms) +✔ limits narrow the definition and never widen it (2.315791ms) +✔ role.launch stays within-role only for the instance the launch block names (4.27587ms) +✔ limits.authority without role.launch leaves the launcher with no launch block (1.543593ms) +✔ limits.authority narrows cross-role actions too (0.920622ms) +✔ classify (0.948873ms) +✔ the record carries what the broker and launcher need (0.853528ms) +✔ digest: key order doesn't matter, any value change does (6.282612ms) +✔ refusals (2.103082ms) +✔ the four shipped version 2 roles load (3.778863ms) +✔ shipped role scopes match addendum B section 2 and the SR runbook (1.259827ms) +✔ shipped authority follows the note's table (0.696413ms) +✔ version 1 files keep loading with no authority (1.18105ms) +✔ the conductor policy isn't a role (0.30637ms) +✔ a missing role file is exit 4, a symbolic link too (0.520046ms) +✔ version 2 refusals (1.75435ms) +✔ authority: closed vocabulary, no gated-only action, no overlap (3.02658ms) +✔ credentials: Gitea scopes (1.209818ms) +✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.62027ms) +✔ credentials: services (0.786607ms) +✔ contract: a non-empty regular Markdown file beside the role file (1.253439ms) +✔ every key names known layers and a merge rule (0.716601ms) +✔ unknown keys and wrong layers refuse (0.606192ms) +✔ types (1.471881ms) +✔ merge: defaults, then the most specific layer wins (0.284825ms) +✔ merge: limits only narrow, and provenance lists each source (0.334036ms) +✔ merge doesn't change its inputs (0.116565ms) +ℹ tests 60 +ℹ suites 0 +ℹ pass 60 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 1840.166773 diff --git a/agents/darkwing/work/s6-review/r3/out/node-cli.txt b/agents/darkwing/work/s6-review/r3/out/node-cli.txt new file mode 100644 index 00000000..dc2e4c41 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/node-cli.txt @@ -0,0 +1,93 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (150.116076ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (143.318043ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (112.64667ms) +✔ decide prints a declining choice as declining (123.3612ms) +✔ an unknown outcome is reported once and never resent (104.492112ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (95.978405ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (103.096033ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (62.80251ms) +✔ every human command refuses inside an agent run before it touches the bus (87.799277ms) +✔ usage errors exit 4; no business and no host is a usage error (98.098188ms) +✔ agents and tasks print through the broker (97.471494ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.758848ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (46.779971ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (29.787435ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (35.958922ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (33.101958ms) +✔ a business without tracker.baseUrl gets no trackers entry (30.721242ms) +✔ an unknown business and a broken system config refuse with exit 3 (54.748785ms) +✔ empty views say so (0.969908ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.121415ms) +✔ tasks print the tracker fields the snapshot carries (0.156025ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (928.595541ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (214.938702ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (152.527975ms) +✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1135.79737ms) +✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (244.109429ms) +✔ a broker reply with no request waiting breaks the channel and the host exits 1 (145.911091ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (222.107069ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (183.54173ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (110.395017ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (153.393648ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (99.687122ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (159.092763ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.422141ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.394443ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (213.690305ms) +✔ bus start refuses with exit 3 without a notifier config (91.015135ms) +✔ bus start runs until bus stop; status reports it while it runs (760.738611ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.073693ms) +✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1322.432308ms) +✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (579.474875ms) +✔ a runner that stops at once ends its launch with the runner's reason (353.845585ms) +✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (698.635983ms) +✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3623.043627ms) +✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (105.759853ms) +✔ an over-long launch request is refused at once, not at the 10 s idle timeout (120.992564ms) +✔ a launch client that never closes its side doesn't hold the host's close (122.451343ms) +✔ a runner that ignores SIGTERM is killed when the host closes (1229.126441ms) +✔ zoned uses the IANA zone across DST (23.875846ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (143.487992ms) +✔ two blocking decisions get two DMs with different nonces (141.62964ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.165734ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (121.478802ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (110.794449ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (104.552061ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (104.544234ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (161.012674ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (128.581453ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (125.688972ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (67.89824ms) +✔ an inbox read failure is logged and the next poll retries (0.653093ms) +✔ no Discord id reaches the journal or the log (99.433677ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (17.950527ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (22.786258ms) +✔ the journal: a whole file that is one torn line truncates to empty (12.167995ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.595513ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.563968ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.762883ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.312306ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.460445ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.364422ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.421262ms) +✔ digest content stays within Discord's 2000 characters (0.255207ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.794729ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (414.716896ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (38.443911ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2145.005169ms) +✔ busExit and refuseInsideAgent (0.411218ms) +✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (257.046804ms) +✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1145.387541ms) +✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (84.449481ms) +✔ launches off and on go to the broker and change the business's launch state (80.802272ms) +✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (80.436899ms) +ℹ tests 83 +ℹ suites 0 +ℹ pass 83 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 8247.060518 diff --git a/agents/darkwing/work/s6-review/r3/out/node-harness.txt b/agents/darkwing/work/s6-review/r3/out/node-harness.txt new file mode 100644 index 00000000..345132bc --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/node-harness.txt @@ -0,0 +1,61 @@ +✔ sessionModel: agent vars win, then the system's execution settings (13.637197ms) +✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.731475ms) +✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.48024ms) +✔ a bundle is written once: an existing file refuses (2.275533ms) +✔ a path with a single quote can't go into the hook command (2.368935ms) +✔ allow exits 0, a deny exits 2 with the reason on stderr (140.177324ms) +✔ a missing or wrong policy, or a bad event, exits 2 (89.826311ms) +✔ the bundle's wrapped command: a missing gate or node still blocks (1101.434813ms) +✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (835.263927ms) +✔ claude: the hook alone blocks a path outside the workspace (490.443322ms) +✔ claude: a second turn resumes the first turn's session (729.198843ms) +✔ claude adapter: --restricted is always passed (5.086875ms) +✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (764.394402ms) +✔ claude: a missing hook or MCP file refuses before claude starts (7.677881ms) +✔ pi: policy tools and typed tools pass, anything else is blocked (3.616418ms) +✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.937758ms) +✔ file tool paths must resolve inside the workspace (1.922599ms) +✔ pi's own path normalisation can't be used to step out (1.648528ms) +✔ a symlink inside the workspace that points out is outside (1.157723ms) +✔ a dangling symlink is refused at any depth, in both harnesses (3.588518ms) +✔ read is checked under every spelling pi's read would open, in both harnesses (16.238233ms) +✔ other spellings cover directories, dangling links and pi's cwd (1.689302ms) +✔ claude path fields per tool (0.684809ms) +✔ glob patterns stay inside the workspace (1.116364ms) +✔ a path that can't be checked is blocked (0.413247ms) +✔ initialize, ping and tools/list (54.370572ms) +✔ tools/call goes through the tool socket; a refusal is an isError result (39.516544ms) +✔ unknown tools and methods are JSON-RPC errors and never reach the socket (34.076324ms) +✔ a missing argument is a usage error (32.619108ms) +✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (395.399077ms) +✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (348.63014ms) +✔ pi: a read is refused when pi would open another spelling outside (351.77974ms) +✔ pi: a missing extension refuses before any model call (6.947637ms) +✔ pi: an extension without its configuration fails pi's start (283.349035ms) +✔ founderCheck: founder variables, then a needed service without a usable token (1.258161ms) +✔ turnRequest names the sender, class, reply and decision (0.250471ms) +✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (232.046776ms) +✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (99.703854ms) +✔ typed tools carry the runner's capability; launch goes to the host's launch socket (408.859537ms) +✔ a RESULT gets no automatic reply; failed turns reply with the reason (1315.829693ms) +✔ SIGTERM during a turn kills the turn's process group and still exits 0 (163.540947ms) +✔ founder credentials stop before the claim (20) (259.39438ms) +✔ a refused claim exits 21; an ended run's capability exits 22 (239.798363ms) +✔ the launch ending under a running session exits 22 (170.336663ms) +✔ a broker that stays unreachable exits 23 after brokerRetries polls (244.161737ms) +✔ a broker that is down at the claim exits 23, not 21 (88.459239ms) +✔ no capability, or a malformed one, on stdin exits 2 (199.612126ms) +✔ a missing or malformed policy exits 2 before the claim (129.299183ms) +✔ the PM gets launch, its task verbs and the reads (8.114929ms) +✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.636439ms) +✔ launch only when the business's launch block names the instance as launcher (1.931234ms) +✔ an action outside the instance's authority has no tool (1.800391ms) +✔ callTool: one JSON line out, the result back, a refusal rejects (8.964112ms) +ℹ tests 53 +ℹ suites 0 +ℹ pass 53 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10356.533 diff --git a/agents/darkwing/work/s6-review/r3/out/node-seat.txt b/agents/darkwing/work/s6-review/r3/out/node-seat.txt new file mode 100644 index 00000000..1830aca5 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/node-seat.txt @@ -0,0 +1,35 @@ +✔ resolveSeat: by name under --repo resolves the repo layout (1.236158ms) +✔ resolveSeat: by path resolves the fleet layout (0.327416ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.720241ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.661439ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.797975ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.246313ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.116987ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (27.532187ms) +✔ CLI launch: --harness lands in the record (32.857114ms) +✔ CLI launch: the launch script's own exit code passes through (31.387682ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (59.054508ms) +✔ CLI launch: omitting --task records an empty string, not null (30.243268ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (85.145837ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (133.457854ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.397508ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.572516ms) +✔ validateRegistration/readRegistration: taskSetBy is optional; a record without it (written before #1511) still loads unchanged; an invalid one is refused; the version does not change (0.728897ms) +✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (0.866342ms) +✔ CLI seat task: --by beats MOSAIC_AGENT_NAME, the environment beats nothing, empty environment is unknown, invalid --by or environment refuses with exit 4 and leaves the record byte for byte (271.441109ms) +✔ family: exactly one launch.max key in the model name, else null (0.663184ms) +✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.653981ms) +✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.49126ms) +✔ session file and launch log: 0600, the session file written once (0.962772ms) +✔ endReason maps the runner's exit codes; a signal is killed (0.119269ms) +✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.234913ms) +✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.007553ms) +✔ a session runs under unshare as pid 1 of its namespace, claims, answers, and stops on mosaic stop (311.632155ms) +ℹ tests 27 +ℹ suites 0 +ℹ pass 27 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 730.855867 diff --git a/agents/darkwing/work/s6-review/r3/out/probe-claude-cwd-dotdot.txt b/agents/darkwing/work/s6-review/r3/out/probe-claude-cwd-dotdot.txt new file mode 100644 index 00000000..bf83ee59 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/probe-claude-cwd-dotdot.txt @@ -0,0 +1,12 @@ +== MODE=read given workspace /data/ws, real /deep/store/ws +exit 0; stdout: mock answer; stderr: +POST /v1/messages: 2 +tool_result sent back: [{"is_error":true,"content":"PreToolUse:Read hook error: [timeout -k 2 10 /usr/bin/node /home/jwoltje/darkwing-scratch/r41c/wt2/packages/harness/src/claude-gate.mjs /bundle/policy.json || exit 2]: mosaic gate: Read path is outside the workspace: /deep/data/ws/secret.txt\n"}] +outside after the run: secret.txt +workspace after the run: +== MODE=write given workspace /data/ws, real /deep/store/ws +exit 0; stdout: mock answer; stderr: +POST /v1/messages: 2 +tool_result sent back: [{"is_error":true,"content":"PreToolUse:Write hook error: [timeout -k 2 10 /usr/bin/node /home/jwoltje/darkwing-scratch/r41c/wt2/packages/harness/src/claude-gate.mjs /bundle/policy.json || exit 2]: mosaic gate: Write path is outside the workspace: /deep/data/ws/planted.txt\n"}] +outside after the run: secret.txt +workspace after the run: diff --git a/agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot-r2.txt b/agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot-r2.txt new file mode 100644 index 00000000..e2279892 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot-r2.txt @@ -0,0 +1,13 @@ +== MODE=read given workspace /data/ws, real /deep/store/ws +gate decide: {"allow":true} +exit 0; stderr: +tool_result sent back: [{"is_error":false,"content":"SECRET-OUTSIDE-THE-WORKSPACE\n"}] +outside after the run: secret.txt +workspace after the run: +== MODE=write given workspace /data/ws, real /deep/store/ws +gate decide: {"allow":true} +exit 0; stderr: +tool_result sent back: [{"is_error":false,"content":"Successfully wrote to ../../data/ws/planted.txt"}] +outside after the run: planted.txt +secret.txt +workspace after the run: diff --git a/agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot.txt b/agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot.txt new file mode 100644 index 00000000..e2279892 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/probe-pi-cwd-dotdot.txt @@ -0,0 +1,13 @@ +== MODE=read given workspace /data/ws, real /deep/store/ws +gate decide: {"allow":true} +exit 0; stderr: +tool_result sent back: [{"is_error":false,"content":"SECRET-OUTSIDE-THE-WORKSPACE\n"}] +outside after the run: secret.txt +workspace after the run: +== MODE=write given workspace /data/ws, real /deep/store/ws +gate decide: {"allow":true} +exit 0; stderr: +tool_result sent back: [{"is_error":false,"content":"Successfully wrote to ../../data/ws/planted.txt"}] +outside after the run: planted.txt +secret.txt +workspace after the run: diff --git a/agents/darkwing/work/s6-review/r3/out/probe-pi-variant.txt b/agents/darkwing/work/s6-review/r3/out/probe-pi-variant.txt new file mode 100644 index 00000000..4f4402a3 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/probe-pi-variant.txt @@ -0,0 +1,16 @@ +== MODE=quote asked n o t e s ' s . t x t +gate decide: {"allow":false,"reason":"mosaic gate: read path is outside the workspace under another spelling: notes's.txt -> \"/home/jwoltje/darkwing-scratch/tmp/r41-pvariant-L0q0/ws/notes’s.txt\""} +exit 0; stderr: +tool_result sent back: [{"is_error":true,"content":"mosaic gate: read path is outside the workspace under another spelling: notes's.txt -> \"/home/jwoltje/darkwing-scratch/tmp/r41-pvariant-L0q0/ws/notes’s.txt\""}] +== MODE=ampm asked s h o t 9 . 4 1 A M . p n g +gate decide: {"allow":false,"reason":"mosaic gate: read path is outside the workspace under another spelling: shot 9.41 AM.png -> \"/home/jwoltje/darkwing-scratch/tmp/r41-pvariant-7MBP/ws/shot 9.41 AM.png\""} +exit 0; stderr: +tool_result sent back: [{"is_error":true,"content":"mosaic gate: read path is outside the workspace under another spelling: shot 9.41 AM.png -> \"/home/jwoltje/darkwing-scratch/tmp/r41-pvariant-7MBP/ws/shot 9.41 AM.png\""}] +== MODE=nfd asked r 303 251 s u m 303 251 . t x t +gate decide: {"allow":false,"reason":"mosaic gate: read path is outside the workspace under another spelling: résumé.txt -> \"/home/jwoltje/darkwing-scratch/tmp/r41-pvariant-BBxV/ws/résumé.txt\""} +exit 0; stderr: +tool_result sent back: [{"is_error":true,"content":"mosaic gate: read path is outside the workspace under another spelling: résumé.txt -> \"/home/jwoltje/darkwing-scratch/tmp/r41-pvariant-BBxV/ws/résumé.txt\""}] +== MODE=plain asked p l a i n . t x t +gate decide: {"allow":false,"reason":"mosaic gate: read path is outside the workspace: plain.txt"} +exit 0; stderr: +tool_result sent back: [{"is_error":true,"content":"mosaic gate: read path is outside the workspace: plain.txt"}] diff --git a/agents/darkwing/work/s6-review/r3/out/probe-spell-edges-survivors.txt b/agents/darkwing/work/s6-review/r3/out/probe-spell-edges-survivors.txt new file mode 100644 index 00000000..a6db3fa7 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/probe-spell-edges-survivors.txt @@ -0,0 +1,18 @@ +== Mw-ampm-case +BAD 1 lowercase am [pi]: allow +BAD 1 lowercase am [claude-code]: allow +== Mx-curly-once +BAD 2 two apostrophes [pi]: allow +BAD 2 two apostrophes [claude-code]: allow +== My-lstat-error-skips +BAD 10 path below a respelled self-loop [pi]: allow +BAD 10 path below a respelled self-loop [claude-code]: allow +== Mz-spell-no-normalise +BAD 3 @ prefix [pi]: allow +BAD 4 NBSP before AM in the asked name [pi]: allow +BAD 5 file:// URL [pi]: allow +BAD 6 file:// URL, %27 [pi]: allow +BAD 3 @ prefix [claude-code]: allow +BAD 4 NBSP before AM in the asked name [claude-code]: allow +BAD 5 file:// URL [claude-code]: allow +BAD 6 file:// URL, %27 [claude-code]: allow diff --git a/agents/darkwing/work/s6-review/r3/out/probe-spell-edges.txt b/agents/darkwing/work/s6-review/r3/out/probe-spell-edges.txt new file mode 100644 index 00000000..a17d4eab --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/probe-spell-edges.txt @@ -0,0 +1,56 @@ +ok 1 lowercase am [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: shot 9.41 am.png -> "/shot 9.41 am) + pi would open "/shot 9.41 am.png" +ok 2 two apostrophes [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: a'b'c.txt -> "/a’b’c.txt") + pi would open "/a’b’c.txt" +ok 3 @ prefix [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: @notes's.txt -> "/notes’s.txt") + pi would open "/notes’s.txt" +ok 4 NBSP before AM in the asked name [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: shot 9.41 AM.png -> "/shot 9.41 AM) + pi would open "/shot 9.41 AM.png" +ok 5 file:// URL [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: file:///notes's.txt -> "/home/jwol) + pi would open "/notes’s.txt" +ok 6 file:// URL, %27 [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: file:///notes%27s.txt -> "/home/jw) + pi would open "/notes’s.txt" +ok 7 respelled dir inside, link out below it [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: dir's/x -> "/dir’s/x") + pi would open "/dir’s/x" +ok 8 respelled dir inside, plain file [pi]: allow + pi would open "/dir’s/x" +ok 9 respelled self-loop [pi]: refuse (mosaic gate: read path can't be checked under another spelling: ELOOP) + pi would open "/loop's" +ok 10 path below a respelled self-loop [pi]: refuse (mosaic gate: read path can't be checked under another spelling: ELOOP) + pi would open "/loop's/x" +ok 11 asked name exists inside, other spelling out [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: notes's.txt -> "/notes’s.txt") + pi would open "/notes's.txt" +ok 12 all families in one name, only AM/PM+NFD+curly on disk [pi]: allow + pi would open "/it's résumé 9.41 PM.txt" +ok 13 NFD+curly with a plain PM [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: it's résumé 9.41 PM.txt -> "/it’s ) + pi would open "/it’s résumé 9.41 PM.txt" +ok 14 ../ws/ form [pi]: refuse (mosaic gate: read path is outside the workspace under another spelling: ../ws/x's.txt -> "/x’s.txt") + pi would open "/x’s.txt" +ok 1 lowercase am [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: shot 9.41 am.png -> "/shot 9.41 am) + pi would open "/shot 9.41 am.png" +ok 2 two apostrophes [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: a'b'c.txt -> "/a’b’c.txt") + pi would open "/a’b’c.txt" +ok 3 @ prefix [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: @notes's.txt -> "/notes’s.txt") + pi would open "/notes’s.txt" +ok 4 NBSP before AM in the asked name [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: shot 9.41 AM.png -> "/shot 9.41 AM) + pi would open "/shot 9.41 AM.png" +ok 5 file:// URL [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: file:///notes's.txt -> "/home/jwol) + pi would open "/notes’s.txt" +ok 6 file:// URL, %27 [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: file:///notes%27s.txt -> "/home/jw) + pi would open "/notes’s.txt" +ok 7 respelled dir inside, link out below it [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: dir's/x -> "/dir’s/x") + pi would open "/dir’s/x" +ok 8 respelled dir inside, plain file [claude-code]: allow + pi would open "/dir’s/x" +ok 9 respelled self-loop [claude-code]: refuse (mosaic gate: Read path can't be checked under another spelling: ELOOP) + pi would open "/loop's" +ok 10 path below a respelled self-loop [claude-code]: refuse (mosaic gate: Read path can't be checked under another spelling: ELOOP) + pi would open "/loop's/x" +ok 11 asked name exists inside, other spelling out [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: notes's.txt -> "/notes’s.txt") + pi would open "/notes's.txt" +ok 12 all families in one name, only AM/PM+NFD+curly on disk [claude-code]: allow + pi would open "/it's résumé 9.41 PM.txt" +ok 13 NFD+curly with a plain PM [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: it's résumé 9.41 PM.txt -> "/it’s ) + pi would open "/it’s résumé 9.41 PM.txt" +ok 14 ../ws/ form [claude-code]: refuse (mosaic gate: Read path is outside the workspace under another spelling: ../ws/x's.txt -> "/x’s.txt") + pi would open "/x’s.txt" diff --git a/agents/darkwing/work/s6-review/r3/out/summary.txt b/agents/darkwing/work/s6-review/r3/out/summary.txt new file mode 100644 index 00000000..70861126 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/summary.txt @@ -0,0 +1,15 @@ +node-harness exit=0 ℹ pass 53 ℹ fail 0 +node-seat exit=0 ℹ pass 27 ℹ fail 0 +node-cli exit=0 ℹ pass 83 ℹ fail 0 +node-bus exit=0 ℹ pass 74 ℹ fail 0 +node-business exit=0 ℹ pass 60 ℹ fail 0 +test-auth exit=0 selftest: 15 passed, 0 failed +test-config exit=0 selftest: 24 passed, 0 failed +test-conductor exit=0 selftest: 17 passed, 0 failed +test-queue exit=0 queue suite: 27 passed, 0 failed +test-foundation exit=0 selftest: 44 passed, 0 failed +test-extension-package exit=0 extension package selftest: 18 passed, 0 failed +test-release exit=0 selftest: 4 passed, 0 failed +test-discord exit=0 discord suite: 66 passed, 0 failed +test-task exit=1 selftest: 26 passed, 2 failed +DONE diff --git a/agents/darkwing/work/s6-review/r3/out/test-auth.txt b/agents/darkwing/work/s6-review/r3/out/test-auth.txt new file mode 100644 index 00000000..ac228760 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/s6-review/r3/out/test-conductor.txt b/agents/darkwing/work/s6-review/r3/out/test-conductor.txt new file mode 100644 index 00000000..8171fda5 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/test-conductor.txt @@ -0,0 +1,55 @@ +Note: switching to '66b9e082e93f94317a4b7a9701ce01dc75cde49d'. + +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 '66b9e082e93f94317a4b7a9701ce01dc75cde49d'. + +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/s6-review/r3/out/test-config.txt b/agents/darkwing/work/s6-review/r3/out/test-config.txt new file mode 100644 index 00000000..76c05ef7 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/s6-review/r3/out/test-discord.txt b/agents/darkwing/work/s6-review/r3/out/test-discord.txt new file mode 100644 index 00000000..b013f9ff --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/s6-review/r3/out/test-extension-package.txt b/agents/darkwing/work/s6-review/r3/out/test-extension-package.txt new file mode 100644 index 00000000..57de7c72 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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: 2333447 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/s6-review/r3/out/test-foundation.txt b/agents/darkwing/work/s6-review/r3/out/test-foundation.txt new file mode 100644 index 00000000..6338e1e6 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/s6-review/r3/out/test-queue.txt b/agents/darkwing/work/s6-review/r3/out/test-queue.txt new file mode 100644 index 00000000..92900b11 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/r41c/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/s6-review/r3/out/test-release-docker.txt b/agents/darkwing/work/s6-review/r3/out/test-release-docker.txt new file mode 100644 index 00000000..7202b85d --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/out/test-release-docker.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/s6-review/r3/out/test-release.txt b/agents/darkwing/work/s6-review/r3/out/test-release.txt new file mode 100644 index 00000000..1d6ed050 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/s6-review/r3/out/test-task.txt b/agents/darkwing/work/s6-review/r3/out/test-task.txt new file mode 100644 index 00000000..85aa46f0 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/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/s6-review/r3/probe/claude-cwd-dotdot.sh b/agents/darkwing/work/s6-review/r3/probe/claude-cwd-dotdot.sh new file mode 100644 index 00000000..e629c570 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/probe/claude-cwd-dotdot.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# pi-cwd-dotdot.sh through a real Claude Code session: the same layout +# ($R/data -> $R/deep/store, workspace given as $R/data/ws), the candidate +# adapter, a settings.json in the bundle's form running the real +# claude-gate.mjs, and a mock API whose first answer calls Read or Write on +# "../../data/ws/X". Dummy key, 127.0.0.1 only. MODE is read or write. +set -eu +WT=$1; OUT=$2 +R=$(mktemp -d "$TMPDIR/r41-ccwd-XXXX") +mkdir -p "$R/home" "$R/sess" "$R/bundle" "$R/deep/store/ws" "$R/deep/data/ws" +ln -s "$R/deep/store" "$R/data" +echo "SECRET-OUTSIDE-THE-WORKSPACE" > "$R/deep/data/ws/secret.txt" +WS="$R/data/ws" +case "$MODE" in + read) TOOL='{"name":"Read","input":{"file_path":"../../data/ws/secret.txt"}}'; TOOLS=Read ;; + write) TOOL='{"name":"Write","input":{"file_path":"../../data/ws/planted.txt","content":"PLANTED\n"}}'; TOOLS=Write ;; +esac +echo "== MODE=$MODE given workspace /data/ws, real /deep/store/ws" | tee -a "$OUT" +echo "the generated prompt" > "$R/bundle/prompt.md" +printf '{"harness":"claude-code","workspace":"%s","tools":["read","write"],"typed":[]}\n' "$WS" > "$R/bundle/policy.json" +printf '{"hooks":{"PreToolUse":[{"matcher":"*","hooks":[{"type":"command","command":"timeout -k 2 10 %s %s %s || exit 2","timeout":20}]}]}}\n' \ + "$(command -v node)" "$WT/packages/harness/src/claude-gate.mjs" "$R/bundle/policy.json" > "$R/bundle/settings.json" +echo '{"mcpServers":{}}' > "$R/bundle/mcp.json" +TOOL_USE=$TOOL node "$(dirname "$0")/mock-api.mjs" "$R/api.log" > "$R/port" & MOCK=$! +while [ ! -s "$R/port" ]; do sleep 0.1; done +set +e +env -i PATH="$PATH" HOME="$R/home" USER="$USER" LANG=C.UTF-8 \ + ANTHROPIC_BASE_URL="http://127.0.0.1:$(cat "$R/port")" ANTHROPIC_API_KEY=sk-ant-dummy-not-a-key \ + DISABLE_TELEMETRY=1 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 MOSAIC_TOOLS=$TOOLS \ + MOSAIC_SYSTEM_PROMPT_FILE="$R/bundle/prompt.md" MOSAIC_REQUEST="Message m1 from jason: read it" \ + MOSAIC_WORKSPACE="$WS" MOSAIC_SESSION_DIR="$R/sess" MOSAIC_MODEL=claude-sonnet-5-5 \ + MOSAIC_CLAUDE_SETTINGS="$R/bundle/settings.json" MOSAIC_CLAUDE_MCP_CONFIG="$R/bundle/mcp.json" \ + timeout 90 /bin/sh "$WT/adapters/claude/adapter.sh" < /dev/null > "$R/stdout" 2> "$R/stderr" +echo "exit $?" > "$R/exit" +kill $MOCK +{ + echo "$(cat "$R/exit"); stdout: $(head -c 200 "$R/stdout"); stderr: $(head -c 300 "$R/stderr")" + echo "POST /v1/messages: $(grep -c '"url":"/v1/messages' "$R/api.log")" + echo "tool_result sent back: $(node -e ' + const ls=require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n").map(l=>JSON.parse(l)).filter(l=>l.url.startsWith("/v1/messages")); + const b=JSON.parse(ls.at(-1).body); const r=b.messages.flatMap(m=>Array.isArray(m.content)?m.content:[]).filter(c=>c.type==="tool_result"); + console.log(JSON.stringify(r.map(c=>({is_error:c.is_error??false,content:c.content}))).split(process.argv[2]).join(""));' "$R/api.log" "$R")" + echo "outside after the run: $(cd "$R/deep/data/ws" && ls)" + echo "workspace after the run: $(cd "$R/deep/store/ws" && ls)" +} | tee -a "$OUT" +rm -rf "$R" diff --git a/agents/darkwing/work/s6-review/r3/probe/mock-api.mjs b/agents/darkwing/work/s6-review/r3/probe/mock-api.mjs new file mode 100644 index 00000000..4ae893af --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/probe/mock-api.mjs @@ -0,0 +1,45 @@ +// A mock Messages API on 127.0.0.1: logs each request body to LOG and +// streams back one short text answer. No real model is reached. +import { createServer } from "node:http"; +import { appendFileSync } from "node:fs"; +const LOG = process.argv[2]; +// TOOL_USE: optional JSON {name, input}; the first streamed answer calls it. +let toolUse = process.env.TOOL_USE ? JSON.parse(process.env.TOOL_USE) : null; +const sse = (res, ev, data) => res.write(`event: ${ev}\ndata: ${JSON.stringify(data)}\n\n`); +const server = createServer((req, res) => { + let body = ""; + req.on("data", (b) => (body += b)); + req.on("end", () => { + appendFileSync(LOG, `${JSON.stringify({ method: req.method, url: req.url, body })}\n`); + if (req.method !== "POST" || !req.url.startsWith("/v1/messages") || req.url.includes("count_tokens")) { + res.writeHead(200, { "content-type": "application/json" }); + return res.end(req.url.includes("count_tokens") ? '{"input_tokens":1}' : "{}"); + } + let stream = false; + try { stream = JSON.parse(body).stream === true; } catch {} + const msg = { id: "msg_mock", type: "message", role: "assistant", model: "claude-sonnet-5-5", content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 1, output_tokens: 1 } }; + if (!stream) { + res.writeHead(200, { "content-type": "application/json" }); + return res.end(JSON.stringify({ ...msg, content: [{ type: "text", text: "mock answer" }], stop_reason: "end_turn" })); + } + res.writeHead(200, { "content-type": "text/event-stream" }); + sse(res, "message_start", { type: "message_start", message: msg }); + if (toolUse) { + const t = toolUse; + toolUse = null; + sse(res, "content_block_start", { type: "content_block_start", index: 0, content_block: { type: "tool_use", id: "toolu_mock1", name: t.name, input: {} } }); + sse(res, "content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "input_json_delta", partial_json: JSON.stringify(t.input) } }); + sse(res, "content_block_stop", { type: "content_block_stop", index: 0 }); + sse(res, "message_delta", { type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: 2 } }); + sse(res, "message_stop", { type: "message_stop" }); + return res.end(); + } + sse(res, "content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }); + sse(res, "content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "mock answer" } }); + sse(res, "content_block_stop", { type: "content_block_stop", index: 0 }); + sse(res, "message_delta", { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: 2 } }); + sse(res, "message_stop", { type: "message_stop" }); + res.end(); + }); +}); +server.listen(0, "127.0.0.1", () => console.log(server.address().port)); diff --git a/agents/darkwing/work/s6-review/r3/probe/pi-cwd-dotdot.sh b/agents/darkwing/work/s6-review/r3/probe/pi-cwd-dotdot.sh new file mode 100644 index 00000000..0ee58694 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/probe/pi-cwd-dotdot.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# The primary check resolves a relative path against the workspace as +# given; Pi resolves it against its cwd, which is the workspace's real path +# (the adapter cds into it and process.cwd() is getcwd). When the given path +# runs through a link whose target sits elsewhere, ".." walks up different +# directories. Layout: +# $R/data -> $R/deep/store (the link, like a symlinked dataRoot) +# $R/deep/store/ws the workspace's real path +# $R/deep/data/ws/secret.txt outside the workspace +# Given workspace $R/data/ws. "../../data/ws/X" is $R/data/ws/X to the +# gate (inside) and $R/deep/data/ws/X to Pi (outside). +# A real Pi session, the candidate adapter and pi-extension.mjs, a mock +# Messages API on 127.0.0.1 and a dummy key. MODE is read or write. +set -eu +WT=$1; OUT=$2 +R=$(mktemp -d "$TMPDIR/r41-cwd-XXXX") +mkdir -p "$R/home" "$R/agent" "$R/bundle" "$R/deep/store/ws" "$R/deep/data/ws" +ln -s "$R/deep/store" "$R/data" +echo "SECRET-OUTSIDE-THE-WORKSPACE" > "$R/deep/data/ws/secret.txt" +WS="$R/data/ws" +case "$MODE" in + read) TOOL='{"name":"read","input":{"path":"../../data/ws/secret.txt"}}' ;; + write) TOOL='{"name":"write","input":{"path":"../../data/ws/planted.txt","content":"PLANTED\n"}}' ;; +esac +echo "== MODE=$MODE given workspace /data/ws, real /deep/store/ws" | tee -a "$OUT" +echo "the generated prompt" > "$R/bundle/prompt.md" +printf '{"harness":"pi","workspace":"%s","tools":["read","write"],"typed":[]}\n' "$WS" > "$R/bundle/policy.json" +echo '[]' > "$R/bundle/tools.json" +TOOL_USE=$TOOL node "$(dirname "$0")/mock-api.mjs" "$R/api.log" > "$R/port" & MOCK=$! +while [ ! -s "$R/port" ]; do sleep 0.1; done +printf '{"providers":{"mock":{"baseUrl":"http://127.0.0.1:%s","api":"anthropic-messages","apiKey":"dummy-not-a-key","models":[{"id":"claude-sonnet-5-5"}]}}}\n' "$(cat "$R/port")" > "$R/agent/models.json" +echo "gate decide: $(WT=$WT node --input-type=module -e ' + const { decide } = await import(process.env.WT + "/packages/harness/src/gate.mjs"); + const t = JSON.parse(process.argv[2]); + console.log(JSON.stringify(decide({harness:"pi",workspace:process.argv[1],tools:["read","write"],typed:[]},t.name,t.input)));' "$WS" "$TOOL")" | tee -a "$OUT" +set +e +env -i PATH="$WT/node_modules/.bin:$PATH" HOME="$R/home" USER="$USER" LANG=C.UTF-8 \ + PI_CODING_AGENT_DIR="$R/agent" PI_PROVIDER=mock PI_MODEL=claude-sonnet-5-5 \ + MOSAIC_SYSTEM_PROMPT_FILE="$R/bundle/prompt.md" MOSAIC_REQUEST="Message m1 from jason: do it" \ + MOSAIC_WORKSPACE="$WS" MOSAIC_TOOLS=read,write \ + MOSAIC_EXTENSIONS="$WT/packages/harness/src/pi-extension.mjs" \ + MOSAIC_POLICY_FILE="$R/bundle/policy.json" MOSAIC_TOOLS_FILE="$R/bundle/tools.json" \ + MOSAIC_TOOL_SOCKET="$R/no-socket" MOSAIC_TURN_MARKER="$R/marker" \ + timeout 90 /bin/sh "$WT/adapters/pi/adapter.sh" < /dev/null > "$R/stdout" 2> "$R/stderr" +echo "exit $?" > "$R/exit" +kill $MOCK +{ + echo "$(cat "$R/exit"); stderr: $(head -c 300 "$R/stderr")" + echo "tool_result sent back: $(node -e ' + const ls=require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n").map(l=>JSON.parse(l)).filter(l=>l.url.startsWith("/v1/messages")); + const b=JSON.parse(ls.at(-1).body); const r=b.messages.flatMap(m=>Array.isArray(m.content)?m.content:[]).filter(c=>c.type==="tool_result"); + console.log(JSON.stringify(r.map(c=>({is_error:c.is_error??false,content:c.content}))).split(process.argv[2]).join(""));' "$R/api.log" "$R")" + echo "outside after the run: $(cd "$R/deep/data/ws" && ls)" + echo "workspace after the run: $(cd "$R/deep/store/ws" && ls)" +} | tee -a "$OUT" +rm -rf "$R" diff --git a/agents/darkwing/work/s6-review/r3/probe/pi-variant.sh b/agents/darkwing/work/s6-review/r3/probe/pi-variant.sh new file mode 100755 index 00000000..04cbafb6 --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/probe/pi-variant.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# Pi's read tool (0.85.1 dist/core/tools/path-utils.js resolveReadPathAsync) +# retries a missing path with macOS spellings: a narrow no-break space +# before AM/PM, NFD, and a curly apostrophe. The gate checks only the path +# as given. A real Pi session, the candidate adapter and pi-extension.mjs, +# --tools read, and a mock Messages API on 127.0.0.1 whose first answer +# reads NAME_ASKED. The workspace holds a link NAME_ON_DISK pointing at a +# file outside. Dummy key; no real model. +set -eu +WT=$1; OUT=$2 +R=$(mktemp -d "$TMPDIR/r41-pvariant-XXXX") +mkdir -p "$R/home" "$R/agent" "$R/ws" "$R/bundle" "$R/outside" +echo "SECRET-OUTSIDE-THE-WORKSPACE" > "$R/outside/secret.txt" +ASKED=$(node -e 'console.log({quote:"notes'"'"'s.txt",ampm:"shot 9.41 AM.png",nfd:"résumé.txt",plain:"plain.txt"}[process.argv[1]])' "$MODE") +DISK=$(node -e 'console.log({quote:"notes’s.txt",ampm:"shot 9.41 AM.png",nfd:"résumé.txt".normalize("NFD"),plain:"plain.txt"}[process.argv[1]])' "$MODE") +ln -s "$R/outside/secret.txt" "$R/ws/$DISK" +echo "== MODE=$MODE asked $(printf %s "$ASKED" | od -An -c | tr -s ' ' | head -c 120)" | tee -a "$OUT" +echo "the generated prompt" > "$R/bundle/prompt.md" +printf '{"harness":"pi","workspace":"%s","tools":["read"],"typed":[]}\n' "$R/ws" > "$R/bundle/policy.json" +echo '[]' > "$R/bundle/tools.json" +TOOL_USE=$(node -e 'console.log(JSON.stringify({name:"read",input:{path:process.argv[1]}}))' "$ASKED") \ + node "$(dirname "$0")/mock-api.mjs" "$R/api.log" > "$R/port" & MOCK=$! +while [ ! -s "$R/port" ]; do sleep 0.1; done +printf '{"providers":{"mock":{"baseUrl":"http://127.0.0.1:%s","api":"anthropic-messages","apiKey":"dummy-not-a-key","models":[{"id":"claude-sonnet-5-5"}]}}}\n' "$(cat "$R/port")" > "$R/agent/models.json" +echo "gate decide: $(WT=$WT node -e ' + const { decide } = await import(process.env.WT + "/packages/harness/src/gate.mjs"); + console.log(JSON.stringify(decide({harness:"pi",workspace:process.argv[1],tools:["read"],typed:[]},"read",{path:process.argv[2]})));' --input-type=module "$R/ws" "$ASKED")" | tee -a "$OUT" +set +e +env -i PATH="$WT/node_modules/.bin:$PATH" HOME="$R/home" USER="$USER" LANG=C.UTF-8 \ + PI_CODING_AGENT_DIR="$R/agent" PI_PROVIDER=mock PI_MODEL=claude-sonnet-5-5 \ + MOSAIC_SYSTEM_PROMPT_FILE="$R/bundle/prompt.md" MOSAIC_REQUEST="Message m1 from jason: read it" \ + MOSAIC_WORKSPACE="$R/ws" MOSAIC_TOOLS=read \ + MOSAIC_EXTENSIONS="$WT/packages/harness/src/pi-extension.mjs" \ + MOSAIC_POLICY_FILE="$R/bundle/policy.json" MOSAIC_TOOLS_FILE="$R/bundle/tools.json" \ + MOSAIC_TOOL_SOCKET="$R/no-socket" MOSAIC_TURN_MARKER="$R/marker" \ + timeout 90 /bin/sh "$WT/adapters/pi/adapter.sh" < /dev/null > "$R/stdout" 2> "$R/stderr" +echo "exit $?" > "$R/exit" +kill $MOCK +{ + echo "$(cat "$R/exit"); stderr: $(head -c 300 "$R/stderr")" + echo "tool_result sent back: $(node -e ' + const ls=require("fs").readFileSync(process.argv[1],"utf8").trim().split("\n").map(l=>JSON.parse(l)).filter(l=>l.url.startsWith("/v1/messages")); + const b=JSON.parse(ls.at(-1).body); const r=b.messages.flatMap(m=>Array.isArray(m.content)?m.content:[]).filter(c=>c.type==="tool_result"); + console.log(JSON.stringify(r.map(c=>({is_error:c.is_error??false,content:c.content}))));' "$R/api.log")" +} | tee -a "$OUT" +rm -rf "$R" diff --git a/agents/darkwing/work/s6-review/r3/probe/spell-edges.mjs b/agents/darkwing/work/s6-review/r3/probe/spell-edges.mjs new file mode 100644 index 00000000..4270dfda --- /dev/null +++ b/agents/darkwing/work/s6-review/r3/probe/spell-edges.mjs @@ -0,0 +1,51 @@ +// Round 3 edges of the R4 spelling check, on decide() alone. Each case +// builds a fresh workspace with one link (or directory) on disk under the +// spelling Pi's read would open, then asks for the plain spelling. Prints +// the decision and, for comparison, what Pi 0.85.1's own resolveReadPath +// would open (run with the workspace's real path as cwd). Imports from the +// review worktree by absolute path (WT). +import { mkdirSync, mkdtempSync, realpathSync, symlinkSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; +const WT = process.env.WT; +const { decide } = await import(`${WT}/packages/harness/src/gate.mjs`); +const { resolveReadPath } = await import(`${WT}/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/path-utils.js`); + +function fresh() { + const dir = mkdtempSync(join(process.env.TMPDIR, "r41c-spell-")); + const ws = join(dir, "ws"); + mkdirSync(ws); + mkdirSync(join(dir, "outside")); + writeFileSync(join(dir, "outside", "secret.txt"), "s"); + writeFileSync(join(ws, "a.txt"), "a"); + return { dir, ws }; +} +function show(name, harness, ws, asked, expect) { + const tool = harness === "pi" ? "read" : "Read"; + const field = harness === "pi" ? "path" : "file_path"; + const d = decide({ harness, workspace: ws, tools: ["read"], typed: [] }, tool, { [field]: asked }); + let opens; + try { opens = resolveReadPath(asked, realpathSync(ws)); } catch (e) { opens = `throws ${e.code}`; } + const got = d.allow ? "allow" : "refuse"; + console.log(`${got === expect ? "ok " : "BAD "} ${name} [${harness}]: ${got}${d.reason ? ` (${d.reason.replace(ws, "").slice(0, 110)})` : ""}`); + console.log(` pi would open ${JSON.stringify(opens.replace?.(ws, "") ?? opens)}`); +} +const out = (dir) => join(dir, "outside", "secret.txt"); + +for (const harness of ["pi", "claude-code"]) { + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "shot 9.41 am.png")); show("1 lowercase am", harness, ws, "shot 9.41 am.png", "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "a’b’c.txt")); show("2 two apostrophes", harness, ws, "a'b'c.txt", "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "notes’s.txt")); show("3 @ prefix", harness, ws, "@notes's.txt", "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "shot 9.41 AM.png")); show("4 NBSP before AM in the asked name", harness, ws, "shot 9.41 AM.png", "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "notes’s.txt")); show("5 file:// URL", harness, ws, pathToFileURL(join(ws, "notes's.txt")).href, "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "notes’s.txt")); show("6 file:// URL, %27", harness, ws, `file://${ws}/notes%27s.txt`, "refuse"); } + { const { dir, ws } = fresh(); mkdirSync(join(ws, "dir’s")); symlinkSync(out(dir), join(ws, "dir’s", "x")); show("7 respelled dir inside, link out below it", harness, ws, "dir's/x", "refuse"); } + { const { dir, ws } = fresh(); mkdirSync(join(ws, "dir’s")); writeFileSync(join(ws, "dir’s", "x"), "x"); show("8 respelled dir inside, plain file", harness, ws, "dir's/x", "allow"); } + { const { ws } = fresh(); symlinkSync("loop’s", join(ws, "loop’s")); show("9 respelled self-loop", harness, ws, "loop's", "refuse"); } + { const { ws } = fresh(); symlinkSync("loop’s", join(ws, "loop’s")); show("10 path below a respelled self-loop", harness, ws, "loop's/x", "refuse"); } + { const { dir, ws } = fresh(); writeFileSync(join(ws, "notes's.txt"), "n"); symlinkSync(out(dir), join(ws, "notes’s.txt")); show("11 asked name exists inside, other spelling out", harness, ws, "notes's.txt", "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "it’s résumé 9.41 PM.txt".normalize("NFD"))); show("12 all families in one name, only AM/PM+NFD+curly on disk", harness, ws, "it's résumé 9.41 PM.txt", "allow"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "it’s résumé 9.41 PM.txt".normalize("NFD"))); show("13 NFD+curly with a plain PM", harness, ws, "it's résumé 9.41 PM.txt", "refuse"); } + { const { dir, ws } = fresh(); symlinkSync(out(dir), join(ws, "x’s.txt")); show("14 ../ws/ form", harness, ws, "../ws/x's.txt", "refuse"); } +} +process.exit(0); diff --git a/agents/darkwing/work/s6-review/review-r3.md b/agents/darkwing/work/s6-review/review-r3.md new file mode 100644 index 00000000..91564020 --- /dev/null +++ b/agents/darkwing/work/s6-review/review-r3.md @@ -0,0 +1,279 @@ +# Row 41, slice 1 S6 (meta-harness and launching), round 3 review (Darkwing) + +Issue #1523, request comment 27012, queue revs 262 and 263 (`a8ee6cc1`). +Packet: `agents/filbert/work/s6/` at `0f38236f`, base `915e00e5`, gate at +`66b9e082`, 42 files. Candidate manifest sha256 +`9f0593af17902e6ec084987175a1e48affd9c71568cb2eeacc7b4cd1ef165cbe`. +Six files changed since round 2: `gate.mjs`, the harness README, +`gate.test.mjs`, `pi-session.test.mjs`, `host.test.mjs` and +`launcher.test.mjs`. Round 2: `review-r2.md`, comment 27010. Sage ruled +that round 3 fixes R4 only and that R1 to R3 hold. + +Verdict: **changes**, comment 27032. R4 is fixed. I checked it against +Pi's code, with 28 edge cases and with a real Pi session, and all of it +holds. My round 2 notes are dealt with. One new finding blocks, R5, and +it isn't in the round 3 code. The gate resolves a relative Pi path against +the workspace path as given, and Pi resolves it against its cwd, which is +the workspace's real path. When the given path runs through a symlink, +`..` climbs different directories. A real Pi session read a file outside +the workspace and wrote a new one there. Round 2's candidate does the +same, and I missed it in both earlier rounds. It sits in the R1 to R3 code +Sage ruled on, so whether it goes in this row is Sage's call. The fix is +small. + +## Method + +- A detached worktree at `66b9e082`, then `git apply --index build.patch` + and `sha256sum -c candidate-manifest.sha256`: 42 OK. After the probes and + mutants I checked again: 42 OK (`r3/mut/manifest-after.txt`). The probes + ran on a second worktree built the same way, so the mutants could run on + the first. +- I read the interdiff against round 2 (`r3/interdiff.patch`, 6 files, + +193/−9), and `gate.mjs` in full. For R4 I reread the pinned Pi 0.85.1's + `dist/utils/paths.js` (`normalizePath`, `resolvePath`), + `dist/core/tools/path-utils.js` (`resolveReadPathAsync`) and + `dist/core/tools/read.js`, and checked that `read` and the CLI's file + arguments are the only callers of the variant lookup. +- Probes in `r3/probe/`. They import from my scratch worktree by absolute + path (`WT`), so they won't run as committed without setting it. Model + calls go to `r3/probe/mock-api.mjs`, a mock Messages API on 127.0.0.1 + with a dummy key; nothing was spent. Claude Code is 2.1.296, Pi is 0.85.1 + from the repository's `node_modules`. +- 29 mutants (`r3/mut/mutate.py`, `run.sh`, `all.sh`): round 2's 22 and + seven on the R4 code. Each ran the node suites of the packages it + touches, and the file was restored from a backup copy. + +Node is v26.8.1, `TMPDIR=~/darkwing-scratch/tmp`. `gate.sh` set +`DOCKER_HOST=unix:///nonexistent.sock`. + +## Suites + +| Suite | Result | +|---|---| +| harness | 53/0 | +| seat | 27/0 | +| cli | 83/0 | +| bus | 74/0 | +| business | 60/0 | +| test-auth | 15/0 | +| test-config | 24/0 | +| test-conductor | 17/0 | +| test-queue | 27/0 | +| test-foundation | 44/0 | +| test-extension-package | 18/0 | +| test-discord | 66/0 | +| test-release | 4/0 with Docker blocked; 14/0 run alone with Docker (`r3/out/test-release-docker.txt`) | +| test-task | 26/2: "user recall run succeeds" and "recalled user name" | + +No test was skipped. The two test-task failures are the live recall, as in +round 2: a real Pi worker in Docker against zai, which would be a paid +call. I didn't make it. + +## R5: a relative Pi path climbs from a different directory (blocking) + +`gate.mjs:71-74`: + +```js +export function insideWorkspace(workspace, p) { + const s = normalise(p); + return within(workspace, isAbsolute(s) ? resolve(s) : resolve(workspace, s)); +} +``` + +`resolve(workspace, s)` is lexical, against the workspace path as the +policy gives it. Pi's path tools resolve with `resolveToCwd` +(`dist/utils/paths.js`), also lexical, but against Pi's cwd. The adapter +runs `cd "$MOSAIC_WORKSPACE"` (`adapters/pi/adapter.sh:26`), and Node's +`process.cwd()` is `getcwd()`, the real path. When the two differ, `..` +climbs different parents. `within()` then compares the real path of what +the gate resolved, which is inside, while Pi opens a path that is outside. + +`spellings()` (line 86) already builds both bases for R4. The primary +check uses one. + +`probe/pi-cwd-dotdot.sh` runs a real Pi session through the candidate +adapter and `pi-extension.mjs`, with the mock API asking for one call. +The layout: + +``` +$R/data -> $R/deep/store a symlink, like a symlinked dataRoot +$R/deep/store/ws the workspace's real path +$R/deep/data/ws/secret.txt outside the workspace +policy and MOSAIC_WORKSPACE $R/data/ws +``` + +`../../data/ws/X` is `$R/data/ws/X` to the gate, inside, and +`$R/deep/data/ws/X` to Pi, outside (`r3/out/probe-pi-cwd-dotdot.txt`): + +``` +read gate {"allow":true}; tool_result "SECRET-OUTSIDE-THE-WORKSPACE" +write gate {"allow":true}; "Successfully wrote to ../../data/ws/planted.txt" + outside after the run: planted.txt secret.txt +``` + +The write needs nothing on disk beforehand except the outside directory. +Every Pi tool with a path field goes through `insideWorkspace`, so edit, +grep, find and ls have it too. The round 2 candidate gives the same two +results (`r3/out/probe-pi-cwd-dotdot-r2.txt`). In round 2 I wrote that +`link/../x` is allowed rightly because Pi resolves `..` the same way the +gate does. That holds only when both start from the same directory, and I +didn't check that they do. + +Claude Code doesn't have it. `probe/claude-cwd-dotdot.sh`, the same layout +through the hook (`r3/out/probe-claude-cwd-dotdot.txt`): Claude Code makes +the path absolute against its real cwd before the hook runs, and the gate +refuses both calls as outside, `/deep/data/ws/...`. + +The precondition is a workspace path that runs through a symlink to a +directory with different parents. `workspaceDir()` +(`packages/seat/src/session.mjs:46`) joins the configured `dataRoot` +without resolving it, and the runner passes that path as the policy's +workspace and as `MOSAIC_WORKSPACE`. A `dataRoot` on a symlink, such as +`~/.mosaic-dev` pointing at a bigger disk, is enough. On this host +`~/.mosaic-dev` is a real directory, so it isn't open here today. + +Fix, either of: + +1. In `insideWorkspace`, resolve a relative path against both the given + and the real workspace path, as `spellings()` does, and require both to + be inside. Checking only the real base would also match Pi, but both + keeps the gate right for a harness whose cwd is the given path. +2. Resolve the workspace once with `realpathSync` where the seat or the + runner builds it, so the policy, the cwd and `MOSAIC_WORKSPACE` all + carry the real path. + +I'd do 1, since the gate shouldn't rely on its caller, and 2 is worth +doing as well. A `gate.test.mjs` case with the layout above in both +harnesses, and a `pi-session.test.mjs` write through it, would hold it. + +## R4: fixed + +`spellings()` (`gate.mjs:84-94`) normalises the path as Pi does, resolves +it against the given and the real workspace path, and builds the four +variants of `resolveReadPathAsync` from each: U+202F before ` AM.` or +` PM.` (case-insensitive, global, like Pi's regex), NFD, `'` to U+2019 +everywhere, and NFD with U+2019. `otherSpelling()` refuses if any variant +exists and resolves outside, goes through a dangling link, or can't be +checked; `ENOTDIR` means the name doesn't exist and is skipped. The check +runs for Pi `read` and Claude `Read`. The other Pi path tools use +`resolveToCwd`, with no variants, so they don't need it. + +It checks every variant that exists, not only the one Pi would open. That +is stricter than Pi in one case: when the asked name exists inside and a +variant links out, Pi opens the asked name and the gate still refuses. I +think that's the right side to err on. + +`probe/spell-edges.mjs` (`r3/out/probe-spell-edges.txt`) runs 14 cases in +each harness and prints what Pi's own `resolveReadPath` would open +alongside the decision. 28 of 28 come out as I expected: + +| Case | Decision | +|---|---| +| lowercase `am`; two apostrophes; an `@` prefix; NBSP before `AM` | refused | +| a `file://` URL, plain and with `%27` | refused | +| a respelled directory inside with a link out below it | refused | +| the same directory holding a plain file | allowed | +| a respelled self-loop, and a path below it | refused, ELOOP | +| the asked name exists inside, a variant links out | refused | +| a name with all three families, only the combined form on disk | allowed; Pi never builds that form | +| NFD and U+2019 with a plain `PM` | refused | +| `../ws/x's.txt` | refused | + +`probe/pi-variant.sh`, round 2's real Pi session, rerun +(`r3/out/probe-pi-variant.txt`): `quote`, `ampm`, `nfd` and `plain` are all +refused, and the tool result Pi sends back is the gate's error. In round 2 +the first three returned the outside file. + +The README's respelling paragraph, the list of Pi files to recheck on an +upgrade and the "Other spellings" limit read correctly. + +## Round 2 notes + +1. **Mr** now has a test: a broker child sends one reply with nothing + waiting, and the host exits 1 with `broker-channel-broken`. It fails + under Mr (below). +2. **Mv**: every launcher test has a 30 s timeout. Under Mv the cli suite now ends in 61 s, with 11 failures and 2 tests cancelled at their timeout, where round 2 hung until my 900 s limit. +3. **Claude `Read`** gets the R4 check, and `spell-edges.mjs` runs every + case through it. +4. **The Ma leftover** isn't traced; Filbert lists it as a follow-up. + It happened again: after the Ma mutant's harness run, a `fake-adapter.mjs` was left running under the user systemd manager. I killed it by PID. + +## Mutants + +`r3/mut/summary.txt` has the raw lines. + +| Mutant | Change | Result | Killed by | +|---|---|---|---| +| Ma | signal handlers installed after setup | harness 50/3, cli 79/1 | the runner's SIGTERM tests | +| Mb | broker records the run before its checks | bus 73/1, cli 83/0 | a restarted broker refuses to rebind an ended run | +| Mc | no `run-ended` refusal on rebind | bus 73/1, cli 81/2 | the rebind test, both host-died-hard tests | +| Md | no instance-running check | cli 81/2 | `bus start --pm`, the PM launches a coder | +| Me | no `authorizeLaunch` before start | cli 82/1 | the PM launches a coder | +| Mf | hook command without `\|\| exit 2` | harness 52/1 | the claude-code bundle test | +| Mg | `founderCheck` finds no founder variables | harness 51/2, cli 83/0 | the unit test and "founder credentials stop before the claim" | +| Mh | no SIGKILL after 30 s in `close()` | cli 82/1 | a runner that ignores SIGTERM | +| Mi | no SIGKILL in `recover()` | cli 82/1 | the host-died-hard test | +| Mj | no 4096-byte stdin cap | harness 52/1 | the stdin capability test | +| Mk | no `LINE_MAX` on `launch.sock` | cli 82/1 | an over-long launch request | +| Ml | no `..` check on glob patterns | harness 52/1 | glob patterns stay inside the workspace | +| Mm | no `launch-revoked` refusal | bus 73/1, cli 83/0 | launches off refuses role.launch | +| Mn | `recover()` doesn't end the run | cli 81/2 | both host-died-hard tests | +| Mo | policy parse back outside the `try` | harness 52/1 | a missing or malformed policy exits 2 | +| Mp | host takes any reply, whatever its id | cli 82/1 | a reply with another id, or none | +| Mq | no 10 s request timer | cli rc 1, 82/0, 1 cancelled | the stall test times out at 30 s, as in round 2 | +| Mr | a reply with nothing waiting is ignored | cli 82/1 | the new test: a reply with no request waiting | +| Ms | `real()` walks with a link-following check | harness 50/3 | the dangling tests and the real Pi write | +| Mt | `close()` doesn't destroy connections | cli 82/1 | a launch client that never closes its side | +| Mu | adapter without `--restricted` | harness 51/2 | both `--restricted` tests | +| Mv | `tag()` doesn't echo the id | bus 73/1, cli 70/11 | 11 fail and 2 are cancelled at their 30 s timeout; the suite ends in 61 s | +| Mw | the AM/PM regex without `i` | harness 53/0 | **survives** | +| Mx | `'` to U+2019 without `g` | harness 53/0 | **survives** | +| My | any `lstat` error skips the spelling | harness 53/0 | **survives** | +| Mz | `spellings()` skips `normalise` | harness 53/0 | **survives** | +| MA | the spelling check for Pi `read` only | harness 52/1 | every spelling, in both harnesses | +| MB | no real-path base in `spellings()` | harness 52/1 | the directories and Pi's cwd test | +| MC | no NFD plus U+2019 | harness 51/2 | the spelling test and the real Pi session | + +25 of 29 killed. All 22 round 2 mutants are killed now, Mr included. +`r3/out/probe-spell-edges-survivors.txt` reruns `spell-edges.mjs` under +each survivor (`r3/mut/survivors.sh`). Each turns a refusal into an +allow, and the cases it flips are the ones I'd add to `gate.test.mjs`: + +| Mutant | Cases that flip to allow, both harnesses | +|---|---| +| Mw | 1, lowercase `am` | +| Mx | 2, two apostrophes | +| My | 10, a path below a respelled self-loop | +| Mz | 3, 4, 5 and 6: `@`, NBSP, `file://` and `%27` | + +Under Mw, Mx and Mz Pi would open the outside file, so these are real +gaps in the tests, not in the code. My is milder: on ELOOP Pi's `access()` +fails too and nothing opens, but the same skip would also pass EACCES. + +## Notes (not blocking) + +1. **Four R4 mutants survive** (above). The code is right, and four + `gate.test.mjs` cases (lowercase `am`, two apostrophes, an `@` or + `file://` path, a path below a respelled loop) would hold it. Since + R5 sends this row back anyway, they're cheap to add in the same round. +2. **Mq** is unchanged: no test fails under it, but the stall test is + cancelled at its 30 s timeout and the suite exits 1. Filbert left it + as it is, and I agree it's caught. +3. **The Ma leftover** reproduced (round 2 note 4). It's a mutant's + side effect, listed as a follow-up. + +## Files + +Round 1 and 2 files stay where they were. Round 3's are under `r3/`. + +- `review-r3.md`: this file. +- `r3/files.txt`, `r3/candidate-manifest.sha256`: the candidate as + reviewed. +- `r3/interdiff.patch`: round 2 against round 3. +- `r3/gate.sh`, `r3/out/`: the suite runs (`summary.txt` first) and every + probe output (`probe-*.txt`). +- `r3/probe/`: `spell-edges.mjs` and round 2's `pi-variant.sh` (R4), + `pi-cwd-dotdot.sh` and `claude-cwd-dotdot.sh` (R5), `mock-api.mjs`. +- `r3/mut/`: `mutate.py`, `run.sh`, `all.sh`, `parse.sh`, `summary.txt`, + one output per mutant and package, `survivors.sh` and + `manifest-after.txt`.