rocko: row 78 round 1 packet, BUILD-LOG and SESSIONS (#1572)

Packet agents/rocko/work/queue-78/ (packet manifest 9e067f40, 32 files,
candidate manifest 3016dbf1), Rocko's BUILD-LOG and SESSIONS appends.
Committed by Sage for Rocko, who has no credential.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 19:10:16 -05:00
co-authored by Claude Opus 5.5
parent 331d037cec
commit 2ece44919e
35 changed files with 1047 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
# Row 78 (#1572): Claude pin default-path tests, candidate packet, round 1
Author: Rocko. Reviewer: Filbert. Brief:
`docs/plans/2026-10-11_claude-pin-and-coder-grant.md`, section "Claude pin
default-path tests" (blob 4784ee65, re-pinned by Sage at revs 567-568). This
covers Filbert's row 72 note N1, comment 27460 on #1566. Base: `bbd22f1b`
(`base.txt`). The packet is uncommitted. I made no commits, pushes or Gitea
calls and read no token or private binding.
## Files (`files.txt`, `candidate-manifest.sha256`)
| File | Change |
|---|---|
| `packages/cli/tests/host.test.mjs` | +1 test: "bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space" |
| `agents/rocko/work/queue-78/mutants.sh` | the mutant rerun: `mutants.sh <tree>` |
There's no change to `scripts/bus-service.sh` or `adapters/claude/adapter.sh`.
## What the test does
It runs `bus-service.sh render` with no `--claude` under `bash`. `PATH` is
the test's own bin dir plus a tools dir holding links to node, dirname, sed
and readlink. This is the isolation row 72's test uses, and here it matters:
this host has a real `/usr/bin/claude`. With `/usr/bin` on `PATH`,
`command -v claude` skips a file without the execute bit and returns that
real binary instead. I checked this in a throwaway directory before the
build.
1. The only `claude` on `PATH` is a regular file, mode 0644. bash's
`command -v` still returns it when no executable `claude` exists.
Render exits 1, stderr says "not an executable file", and stdout is
empty.
2. `claude`, mode 0755, sits in a `PATH` dir named `spaced bin`. Render
exits 1, stderr says "cannot carry" and names the full path, and stdout
is empty.
As the brief expected, the code refuses both cases. There is no deviation,
unlike row 72's directory case.
## Mutants (`out/mutants-candidate.txt`, `out/mutants-base.txt`)
The existing `--claude` tests already exercise both checks for a given pin.
Dropping either check outright is killed on the base, so it says nothing
about the default path. Each mutant here instead makes one check skip only
the default (`CLAUDE_SET=0`) and keeps it for `--claude`:
| Mutant | Edit in `scripts/bus-service.sh` | Base `bbd22f1b` | Candidate |
|---|---|---|---|
| `default-skips-exec-check` | `[ "$CLAUDE_SET" = 0 ] \|\| { [ -f "$bin" ] && [ -x "$bin" ]; } \|\| die …` | **survived** | killed: host.test.mjs:640 (case 1's exit status) |
| `default-skips-char-check` | `case "$bin" in *[!A-Za-z0-9/._+-]*) [ "$CLAUDE_SET" = 0 ] \|\| die … ;; esac` | **survived** | killed: host.test.mjs:647 (case 2's exit status) |
The script saves the file, applies the sed and checks it changed something.
It then runs `packages/cli/tests/host.test.mjs` and restores the file from
the saved copy. The base run used a clean detached worktree at `bbd22f1b`,
which I removed afterwards. `out/mutants-*/` holds each diff and log. The
script exits 1 on the base, as it should when a mutant survives.
I didn't add a mutant for the absolute-path check. `command -v` returns an
absolute path unless a `PATH` entry is relative, and the brief doesn't name
that case.
## Gate (`out/gate/`, sequential, scratch worktree at bbd22f1b with the candidate, `node_modules` linked)
Run 00:05:37Z-00:09:03Z.
| Suite | Result |
|---|---|
| cli `node --test 'packages/cli/tests/*.test.mjs'` | 114 pass, 0 fail, 0 skipped |
| test-auth | 15 passed, 0 failed |
| test-conductor | 17 passed, 0 failed |
| test-config | 24 passed, 0 failed |
| test-discord | 66 passed, 0 failed |
| test-extension-package | 18 passed, 0 failed |
| test-foundation | 44 passed, 0 failed |
| test-queue | 27 passed, 0 failed |
| test-release | 14 passed, 0 failed |
| test-task | 98 passed, 0 failed (Docker) |
The cli count is 114 against 113 at row 72's landing; the one added test is
this row's. After the gate I removed the `rocko-r78_default` network.
+1
View File
@@ -0,0 +1 @@
bbd22f1b09a0f4d9defc15bc3153976a28801f20
+95
View File
@@ -0,0 +1,95 @@
diff --git a/agents/rocko/work/queue-78/mutants.sh b/agents/rocko/work/queue-78/mutants.sh
new file mode 100755
index 00000000..0909b5b5
--- /dev/null
+++ b/agents/rocko/work/queue-78/mutants.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Row 78 (#1572): rerun the mutants against a tree. Usage: mutants.sh <tree>.
+# Each mutant makes one check in scripts/bus-service.sh skip the default pin
+# (no --claude) while still applying to --claude, runs the cli host tests,
+# and restores the file from a saved copy, so the tree need not be
+# committed. Prints "killed" or "SURVIVED" per mutant; exit 1 if any
+# survives, a sed doesn't change the file, or the unmutated run fails.
+set -euo pipefail
+TREE="$(cd "${1:?usage: mutants.sh <tree>}" && pwd)"
+cd "$TREE"
+SCRIPT=scripts/bus-service.sh
+TESTS=(packages/cli/tests/host.test.mjs)
+run_tests() { node --test "${TESTS[@]}" >"$1" 2>&1; }
+
+# name | sed expression
+MUTANTS=(
+ 'default-skips-exec-check|s/^ \[ -f "\$bin" \] && \[ -x "\$bin" \] || die / [ "$CLAUDE_SET" = 0 ] || { [ -f "$bin" ] \&\& [ -x "$bin" ]; } || die /'
+ 'default-skips-char-check|s/^ case "\$bin" in \*\[!A-Za-z0-9\/._+-\]\*) die / case "$bin" in *[!A-Za-z0-9\/._+-]*) [ "$CLAUDE_SET" = 0 ] || die /'
+)
+
+OUT="${OUT:-$TREE/.mutants}"
+mkdir -p "$OUT"
+run_tests "$OUT/baseline.log" || { echo "baseline run failed: $OUT/baseline.log" >&2; exit 1; }
+echo "baseline: pass"
+status=0
+for m in "${MUTANTS[@]}"; do
+ IFS='|' read -r name expr <<<"$m"
+ cp -p "$SCRIPT" "$OUT/orig"
+ sed -i -e "$expr" "$SCRIPT"
+ if cmp -s "$OUT/orig" "$SCRIPT"; then
+ echo "$name: NOT APPLIED (sed matched nothing)"; status=1; continue
+ fi
+ diff -u "$OUT/orig" "$SCRIPT" >"$OUT/$name.diff" || true
+ if run_tests "$OUT/$name.log"; then
+ echo "$name: SURVIVED"; status=1
+ else
+ echo "$name: killed ($(grep -E '^✖' "$OUT/$name.log" | grep -v 'failing tests' | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';'))"
+ fi
+ cp -p "$OUT/orig" "$SCRIPT"
+ cmp -s "$OUT/orig" "$SCRIPT" || { echo "restore failed: $SCRIPT" >&2; exit 1; }
+ rm -f "$OUT/orig"
+done
+exit "$status"
diff --git a/packages/cli/tests/host.test.mjs b/packages/cli/tests/host.test.mjs
index 13a621ae..6f0961af 100644
--- a/packages/cli/tests/host.test.mjs
+++ b/packages/cli/tests/host.test.mjs
@@ -611,3 +611,41 @@ test("bus-service.sh refuses a directory pin and keeps a symlink default unresol
assert.equal(pinned(r.stdout), join(linkBin, "claude"));
assert.ok(!r.stdout.includes(real), "the target never reaches the unit");
});
+
+// Row 78 (#1572): the default from `command -v claude` takes the same checks
+// as --claude. PATH holds only the test's dirs, so a real claude elsewhere on
+// the host can't stand in for the one under test.
+test("bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space", (t) => {
+ const dir = tmp(t);
+ const script = join(REPO, "scripts", "bus-service.sh");
+ const tools = join(dir, "tools");
+ mkdirSync(tools);
+ symlinkSync(process.execPath, join(tools, "node"));
+ for (const tool of ["dirname", "sed", "readlink"]) {
+ const at = ["/usr/bin", "/bin"].map((d) => join(d, tool)).find((p) => existsSync(p));
+ symlinkSync(at, join(tools, tool));
+ }
+ const bash = ["/usr/bin/bash", "/bin/bash"].find((p) => existsSync(p));
+ const run = (bin) => spawnSync(bash, [script, "render"], { encoding: "utf8", env: { PATH: `${bin}:${tools}`, HOME: dir } });
+ const stand = (bin, mode) => {
+ mkdirSync(bin);
+ writeFileSync(join(bin, "claude"), '#!/bin/sh\necho "9.9.9 (Claude Code)"\n');
+ chmodSync(join(bin, "claude"), mode);
+ return bin;
+ };
+
+ // The only claude on PATH has no execute bit: `command -v` still returns
+ // it, and the -x check refuses it.
+ let r = run(stand(join(dir, "noexec-bin"), 0o644));
+ assert.equal(r.status, 1);
+ assert.match(r.stderr, /not an executable file/);
+ assert.equal(r.stdout, "");
+
+ // claude in a PATH dir with a space: systemd would split the pin there.
+ const spaced = stand(join(dir, "spaced bin"), 0o755);
+ r = run(spaced);
+ assert.equal(r.status, 1);
+ assert.match(r.stderr, /cannot carry/);
+ assert.ok(r.stderr.includes(join(spaced, "claude")), r.stderr);
+ assert.equal(r.stdout, "");
+});
@@ -0,0 +1,2 @@
c66ad9b1f058c19cce3e02083c6bf3b2122cc3d1897ea48c0f02c188c6b99c10 agents/rocko/work/queue-78/mutants.sh
13c473d7cd252c5e3492e26cde273d40e259fda2ef75553dfa9cdfe3b1780797 packages/cli/tests/host.test.mjs
+2
View File
@@ -0,0 +1,2 @@
agents/rocko/work/queue-78/mutants.sh
packages/cli/tests/host.test.mjs
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Row 78 (#1572): rerun the mutants against a tree. Usage: mutants.sh <tree>.
# Each mutant makes one check in scripts/bus-service.sh skip the default pin
# (no --claude) while still applying to --claude, runs the cli host tests,
# and restores the file from a saved copy, so the tree need not be
# committed. Prints "killed" or "SURVIVED" per mutant; exit 1 if any
# survives, a sed doesn't change the file, or the unmutated run fails.
set -euo pipefail
TREE="$(cd "${1:?usage: mutants.sh <tree>}" && pwd)"
cd "$TREE"
SCRIPT=scripts/bus-service.sh
TESTS=(packages/cli/tests/host.test.mjs)
run_tests() { node --test "${TESTS[@]}" >"$1" 2>&1; }
# name | sed expression
MUTANTS=(
'default-skips-exec-check|s/^ \[ -f "\$bin" \] && \[ -x "\$bin" \] || die / [ "$CLAUDE_SET" = 0 ] || { [ -f "$bin" ] \&\& [ -x "$bin" ]; } || die /'
'default-skips-char-check|s/^ case "\$bin" in \*\[!A-Za-z0-9\/._+-\]\*) die / case "$bin" in *[!A-Za-z0-9\/._+-]*) [ "$CLAUDE_SET" = 0 ] || die /'
)
OUT="${OUT:-$TREE/.mutants}"
mkdir -p "$OUT"
run_tests "$OUT/baseline.log" || { echo "baseline run failed: $OUT/baseline.log" >&2; exit 1; }
echo "baseline: pass"
status=0
for m in "${MUTANTS[@]}"; do
IFS='|' read -r name expr <<<"$m"
cp -p "$SCRIPT" "$OUT/orig"
sed -i -e "$expr" "$SCRIPT"
if cmp -s "$OUT/orig" "$SCRIPT"; then
echo "$name: NOT APPLIED (sed matched nothing)"; status=1; continue
fi
diff -u "$OUT/orig" "$SCRIPT" >"$OUT/$name.diff" || true
if run_tests "$OUT/$name.log"; then
echo "$name: SURVIVED"; status=1
else
echo "$name: killed ($(grep -E '^✖' "$OUT/$name.log" | grep -v 'failing tests' | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';'))"
fi
cp -p "$OUT/orig" "$SCRIPT"
cmp -s "$OUT/orig" "$SCRIPT" || { echo "restore failed: $SCRIPT" >&2; exit 1; }
rm -f "$OUT/orig"
done
exit "$status"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -u
W=/mnt/storage/scratch/rocko-r78; G=/mnt/storage/scratch/rocko-r78-out/gate
mkdir -p $G; cd $W
date -u +%Y-%m-%dT%H:%M:%SZ > $G/start.txt
: > $G/summary.txt
for p in cli; do
node --test "packages/$p/tests/*.test.mjs" > $G/$p.log 2>&1; rc=$?
echo "$p rc=$rc $(grep -E '^ℹ (pass|fail|skipped)' $G/$p.log | tr '\n' ' ')" >> $G/summary.txt
done
for s in scripts/test-*.sh; do
n=$(basename $s .sh)
bash $s > $G/$n.log 2>&1; rc=$?
echo "$n rc=$rc $(grep -iE '[0-9]+ passed|passed.*failed|failed' $G/$n.log | tail -1)" >> $G/summary.txt
done
date -u +%Y-%m-%dT%H:%M:%SZ > $G/end.txt
+124
View File
@@ -0,0 +1,124 @@
✔ every screen and overlay fits 120x36, 80x24 and the narrow layout, row for row (1910.633632ms)
✔ width counts terminal cells: wide CJK and emoji 2, U+FE0F after a one-cell base 1, marks 0 (143.112029ms)
✔ broker text is shown, never interpreted: no raw control character or bidi override reaches the terminal (279.826101ms)
✔ NO_COLOR: no escape codes, except reverse video on the selected row (391.035639ms)
✔ 1-9 shows the decide flow's own lines and question; only y resolves (67.853213ms)
✔ the app shows the same decide text as mosaic decide (31.944793ms)
✔ any key but y or Y at the question answers No; nothing is sent (433.56893ms)
✔ below 40x10 the question is hidden, so every key answers it No, y included (Filbert R3) (123.841203ms)
✔ a throw while answering the question goes to crash, sends nothing and leaves no job pending (33.003865ms)
✔ type-ahead never answers the question: a paste, keys typed before it is drawn, keys inside askMs (506.38546ms)
✔ quitting at the question answers No and sends nothing, armed or not (89.764123ms)
✔ a timer tick reads nothing under an overlay, never answers the question, and keeps a resolved status (72.091968ms)
✔ a long question is cut inside the question; the action, target, choice and prompt stay (Dewey B1) (73.630176ms)
✔ a decide box too big for the terminal says so, and every key answers it No, y included (Dewey B1, round 3) (461.801402ms)
✔ at every size the decide box shows each line y depends on, or says it is hidden (15606.511378ms)
✔ outcome unknown: the CLI's sentence, no retry, then the trail or a re-read (85.695605ms)
✔ refused and failed reads keep the last list, marked stale; a refused decide says refused (38.38841ms)
✔ keys: views, moving, trail of a selection, the command line, help and quit (118.586541ms)
✔ one option: the status line says Press 1, not 1-1 (32.072393ms)
✔ no business: the app says so and :business picks one (14.585079ms)
✔ mosaic with no argument: exit 3 in an agent run, exit 4 with the usage off a terminal, the app on one (2.603454ms)
✔ scripts/mosaic with no argument reaches the app entry; the seat usage it printed before has not drifted (262.887666ms)
✔ strings to keep exactly live once, in the decide flow and the transport; the app does not restate them (0.670846ms)
✔ decide with --note still asks; a No sends nothing (12.425329ms)
✔ the app's business comes from a live bus host only (1.266359ms)
✔ runApp leaves the terminal as it found it on q, end of input, SIGTERM, SIGINT, SIGHUP and an error, the first frame's included (10.630551ms)
✔ draw is pure: same state, same frame (1.002736ms)
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (62.395894ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (41.83927ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (37.272261ms)
✔ decide prints a declining choice as declining (27.334962ms)
✔ an unknown outcome is reported once and never resent (27.982566ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (18.589117ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (21.540599ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.229738ms)
✔ every human command refuses inside an agent run before it touches the bus (15.049641ms)
✔ usage errors exit 4; no business and no host is a usage error (25.282199ms)
✔ agents and tasks print through the broker (29.350635ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.599913ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (93.001014ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (165.337945ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (81.63954ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (59.362413ms)
✔ a business without tracker.baseUrl gets no trackers entry (57.918774ms)
✔ an unknown business and a broken system config refuse with exit 3 (80.860704ms)
✔ empty views say so (1.313027ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.574141ms)
✔ tasks print the tracker fields the snapshot carries (0.285888ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1077.952051ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (230.359933ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (118.99623ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1137.198972ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (278.437063ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (134.075152ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (368.342365ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (252.110471ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (145.015451ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (275.148147ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (182.368233ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (281.79552ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (49.417709ms)
✔ bus stop refuses to signal a live pid that is not a bus host (205.384672ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (499.726888ms)
✔ bus start refuses with exit 3 without a notifier config (274.274829ms)
✔ bus start runs until bus stop; status reports it while it runs (1608.950298ms)
✔ bus-service.sh renders the unit and installs it into a given directory (94.865731ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (129.718873ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (28.45915ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (14.909042ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1330.679579ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (554.938523ms)
✔ a runner that stops at once ends its launch with the runner's reason (264.614384ms)
✔ 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 (850.041311ms)
✔ 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 (4327.987734ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (129.833428ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (128.980928ms)
✔ a launch client that never closes its side doesn't hold the host's close (143.852901ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1247.661366ms)
✔ claudeVersion asks MOSAIC_CLAUDE_BIN when set, else claude on PATH (9.161909ms)
✔ zoned uses the IANA zone across DST (29.406915ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (52.684929ms)
✔ two blocking decisions get two DMs with different nonces (39.514614ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.369995ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (47.590052ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (37.063874ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.513517ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.30849ms)
✔ 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 (159.898503ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (68.339892ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.286448ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.414629ms)
✔ an inbox read failure is logged and the next poll retries (0.855958ms)
✔ no Discord id reaches the journal or the log (11.39981ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.747398ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.385693ms)
✔ the journal: a whole file that is one torn line truncates to empty (1.625335ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.930316ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.075788ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.793322ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.668318ms)
✔ 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.956293ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.713547ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.886156ms)
✔ digest content stays within Discord's 2000 characters (0.499336ms)
✔ runLoop never overlaps ticks and stops after the one in flight (112.451267ms)
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 (442.568081ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (79.165162ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2362.637568ms)
✔ busExit and refuseInsideAgent (0.811859ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (89.574109ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1039.819465ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (14.305242ms)
✔ launches off and on go to the broker and change the business's launch state (12.907024ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (14.709084ms)
ℹ tests 114
ℹ suites 0
ℹ pass 114
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 21154.213603
@@ -0,0 +1 @@
2026-10-12T00:09:03Z
@@ -0,0 +1 @@
2026-10-12T00:05:37Z
@@ -0,0 +1,10 @@
cli rc=0 ℹ pass 114 ℹ fail 0 ℹ skipped 0
test-auth rc=0 selftest: 15 passed, 0 failed
test-conductor rc=0 selftest: 17 passed, 0 failed
test-config rc=0 selftest: 24 passed, 0 failed
test-discord rc=0 discord suite: 66 passed, 0 failed
test-extension-package rc=0 extension package selftest: 18 passed, 0 failed
test-foundation rc=0 selftest: 44 passed, 0 failed
test-queue rc=0 queue suite: 27 passed, 0 failed
test-release rc=0 selftest: 14 passed, 0 failed
test-task rc=0 selftest: 98 passed, 0 failed
@@ -0,0 +1,17 @@
OK status with missing harness credential exits 3 and still lists accounts
OK status reports harness credential (read-only) + mosaic accounts
OK api key material never reaches output
OK oauth token material never reaches output
OK unparseable credential file exits 2
OK symlinked credential file exits 4
OK env-side credential names reported
OK env var values never reach output
OK accounts without an accounts dir reports none and creates nothing
OK accounts lists files and marks the active one
OK loose account perms flagged in listing
OK agent --auth with missing account file refuses (exit 4)
OK agent --auth with non-0600 account file refuses
OK agent --auth with invalid account name refuses
OK auth.sh without valid config refuses
selftest: 15 passed, 0 failed
@@ -0,0 +1,23 @@
On branch refactor
Your branch is up to date with 'origin/refactor'.
nothing to commit, working tree clean
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0)
OK apply: attribution in commit subject
OK apply: target tree clean after commit
OK disallowed path refused (exit 1)
OK disallowed path: target untouched
OK syntax gate refused broken .mjs (exit 1)
OK syntax gate: target untouched
OK suite failure refused (exit 1)
OK suite failure: target reverted to clean
OK disabled policy refused (exit 2)
OK disabled policy: target untouched
OK failed run refused (exit 1)
OK failed run: target untouched
OK missing run exits 4 (exit 4)
OK invalid policy exits 2 (exit 2)
selftest: 17 passed, 0 failed
@@ -0,0 +1,26 @@
OK absent adapter defaults to pi
OK adapter mock validates (exit 0)
OK unsupported adapter exits 2 (exit 2)
OK env exports adapter
OK bootstrap creates default when absent (exit 0)
OK bootstrap wrote config file
OK bootstrap is idempotent on existing config (exit 0)
OK bootstrap did not rewrite existing config
OK validate missing config exits 3 (exit 3)
OK malformed JSON exits 2 (exit 2)
OK unsupported configVersion exits 2 (exit 2)
OK unknown top-level key exits 2 (exit 2)
OK unknown execution key exits 2 (exit 2)
OK unsupported backend exits 2 (exit 2)
OK unsupported environment exits 2 (exit 2)
OK relative dataRoot exits 2 (exit 2)
OK non-canonical dataRoot exits 2 (exit 2)
OK filesystem root dataRoot exits 2 (exit 2)
OK home directory dataRoot exits 2 (exit 2)
OK dataRoot containing config dir exits 2 (exit 2)
OK control character in provider exits 2 (exit 2)
OK symlinked config file exits 2 (exit 2)
OK env exports resolve correctly
OK failed validation modified nothing
selftest: 24 passed, 0 failed
@@ -0,0 +1,70 @@
toolchain: node v26.8.1
OK syntax: packages/discord/src/approvals.mjs
OK syntax: packages/discord/src/authorize.mjs
OK syntax: packages/discord/src/binding.mjs
OK syntax: packages/discord/src/cli.mjs
OK syntax: packages/discord/src/connector.mjs
OK syntax: packages/discord/src/context.mjs
OK syntax: packages/discord/src/engine-pi.mjs
OK syntax: packages/discord/src/errors.mjs
OK syntax: packages/discord/src/gateway.mjs
OK syntax: packages/discord/src/git.mjs
OK syntax: packages/discord/src/journal.mjs
OK syntax: packages/discord/src/notify.mjs
OK syntax: packages/discord/src/rest.mjs
OK syntax: packages/discord/src/setspark.mjs
OK syntax: packages/discord/src/tools.mjs
OK syntax: packages/discord/src/web.mjs
OK syntax: packages/discord/bin/git-credential.mjs
OK syntax: packages/discord/extension/tools.mjs
OK syntax: packages/discord/tests/approvals.test.mjs
OK syntax: packages/discord/tests/authorize.test.mjs
OK syntax: packages/discord/tests/binding.test.mjs
OK syntax: packages/discord/tests/connector.test.mjs
OK syntax: packages/discord/tests/context.test.mjs
OK syntax: packages/discord/tests/engine.test.mjs
OK syntax: packages/discord/tests/fake-pi.mjs
OK syntax: packages/discord/tests/gateway.test.mjs
OK syntax: packages/discord/tests/git.test.mjs
OK syntax: packages/discord/tests/helpers.mjs
OK syntax: packages/discord/tests/journal.test.mjs
OK syntax: packages/discord/tests/notify.test.mjs
OK syntax: packages/discord/tests/recover.test.mjs
OK syntax: packages/discord/tests/rest.test.mjs
OK syntax: packages/discord/tests/setspark.test.mjs
OK syntax: packages/discord/tests/tools.test.mjs
OK syntax: packages/discord/tests/web.test.mjs
OK syntax: packages/discord/fixtures/claim-worker.mjs
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
OK syntax: scripts/discord.sh
OK syntax: scripts/discord-service.sh
OK packages/discord declares no dependencies
OK no bot-token-shaped string in packages/discord
OK fixture binding uses placeholder ids only
OK fixture binding validates
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
OK real pi with protocol vault adds reserve_id to the git verbs
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
OK real pi refuses a git key on a read-only root (fail closed)
OK real pi with the pilot flags (--no-tools) exposes no tool at all
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test packages/discord/tests/ (ℹ pass 178)
OK scripts/discord.sh --help exits 0
OK scripts/discord.sh check without a binding exits 4
OK scripts/discord.sh recover without a binding exits 4
OK scripts/discord.sh reload without a binding exits 4
OK scripts/discord-service.sh without a command exits 4
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
OK service install writes the rendered unit (0644) and leaves no temp file
OK service install a second time reports unchanged
OK systemd-analyze verify accepts the rendered unit
OK service uninstall removes the unit file
OK service install with an unknown flag exits 4
OK service install with USER unset finishes and names the account for lingering
discord suite: 66 passed, 0 failed
@@ -0,0 +1,21 @@
OK initial ordinary-file install
OK installed tree matches canonical source
OK installed tree has no symlinks
OK check detects installation drift
OK sync refuses to overwrite installation drift
OK check detects an extra destination file
OK check detects an extra destination directory
OK check rejects a destination symlink
OK sync accepts a canonical source update
OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 2112099 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation
OK interrupted replacement rolled back
OK sync succeeds after interruption recovery
OK unlocked stale lock file does not block
OK active lock refuses a concurrent sync
OK source symlink fails closed
OK nested second entrypoint fails closed
extension package selftest: 18 passed, 0 failed
@@ -0,0 +1,53 @@
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
OK syntax: scripts/foundation-inspect.mjs
OK syntax: scripts/foundation/strict-json.mjs
OK syntax: scripts/foundation/canonical.mjs
OK syntax: scripts/foundation/resolve.mjs
OK syntax: scripts/foundation/validate-record.mjs
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
OK syntax: scripts/foundation/canonical.test.mjs
OK syntax: scripts/foundation/cli.test.mjs
OK syntax: scripts/foundation/fixtures.test.mjs
OK syntax: scripts/foundation/resolve.test.mjs
OK syntax: scripts/foundation/strict-json.test.mjs
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
OK fixture generator runs
OK checked-in fixtures/bundles equal a fresh generation
OK checked-in fixtures/raw equal a fresh generation
OK checked-in fixtures/index.json equal a fresh generation
OK checked-in demo bundles equal a fresh generation
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
OK node --test scripts/foundation/ (ℹ pass 80)
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
OK oracle: zero schema-column disagreements with the pinned checker
OK oracle: strict-only profile refusals are counted and asserted
OK demo: permitted read preview exits 0 (exit 0)
OK demo: permitted file.change preview exits 0 (exit 0)
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
OK demo: revoked registration is refused (exit 3) (exit 3)
OK demo: message is not authority (exit 3) (exit 3)
OK usage: no arguments exits 2 (exit 2)
OK io: missing file exits 4 (exit 4)
OK io: directory exits 4 (exit 4)
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
OK bound: oversize fixture exits 2 (exit 2)
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
OK text output starts with the disclaimer
OK json output is valid JSON with result allowed and exactly the charter §7 fields
OK json golden matches byte-for-byte
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
OK canary never printed (bundle run and credential-file run)
OK a non-bundle JSON file is refused at the shape gate, not read into output
OK no field of the non-bundle file is echoed
selftest: 44 passed, 0 failed
@@ -0,0 +1,35 @@
toolchain: node v26.8.1, git version 2.55.0
OK syntax: packages/queue/src/cli.mjs
OK syntax: packages/queue/src/errors.mjs
OK syntax: packages/queue/src/io.mjs
OK syntax: packages/queue/src/lock.mjs
OK syntax: packages/queue/src/queue.mjs
OK syntax: packages/queue/src/review.mjs
OK syntax: packages/queue/src/store.mjs
OK syntax: packages/queue/tests/commit.test.mjs
OK syntax: packages/queue/tests/data.test.mjs
OK syntax: packages/queue/tests/dispatch.test.mjs
OK syntax: packages/queue/tests/helpers.mjs
OK syntax: packages/queue/tests/lock.test.mjs
OK syntax: packages/queue/tests/migration.test.mjs
OK syntax: packages/queue/tests/review.test.mjs
OK syntax: packages/queue/tests/store.test.mjs
OK syntax: packages/queue/tests/write.test.mjs
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
OK syntax: scripts/queue-commit.sh
OK syntax: scripts/git-hooks/pre-commit
OK syntax: scripts/mosaic
OK queue-commit.sh, the guard and scripts/mosaic are executable
OK packages/queue declares no dependencies
ℹ tests 150
ℹ pass 150
ℹ fail 0
OK node --test packages/queue/tests/
OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/mnt/storage/scratch/rocko-r78) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed
@@ -0,0 +1,16 @@
OK valid RELEASE resolves (exit 0)
OK invalid RELEASE exits 1 (exit 1)
OK missing RELEASE exits 1 (exit 1)
OK valid RELEASE leaves image tag consistent with version
OK status safe on empty state (exit 0)
OK status created no pointer
OK fault-injected activation refuses (exit 1)
OK refused activation wrote no pointer
OK refusal logged exactly once with valid fields
OK healthy activation succeeds (exit 0)
OK pointer written with valid fields
OK repeat activation succeeds (log grows) (exit 0)
OK log is append-only across activations
OK rollback without previous refuses (exit 1)
selftest: 14 passed, 0 failed
@@ -0,0 +1,101 @@
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)
OK mock adapter: gate passes on matching mock response (exit 0)
OK mock adapter: expect-mismatch recorded (exit 1)
OK mismatch reason recorded
OK unknown adapter fails closed (exit 1)
OK mission task runs via mock adapter (exit 0)
OK mission section injected into generated prompt
OK retry of mission run succeeds (exit 0)
OK retriedFrom lineage recorded
OK mission section present after retry (relative path resolved)
OK retry of missing run exits 4 (exit 4)
OK skill install bundled ms-tools (exit 0)
OK installed to skills-available
OK double install refuses (exit 1)
OK activate enables skill (exit 0)
OK enabled dir populated
OK uninstall while enabled refuses (exit 1)
OK deactivate moves back to available (exit 0)
OK uninstall removes from available (exit 0)
OK seat with enabled skill launches
OK skill path delivered to adapter
OK shipped researcher contract resolves (ceiling + network)
OK resolve-role refuses a non-role document (conductor policy) (exit 2)
OK resolve-role refuses name/filename mismatch (exit 2)
OK resolve-role refuses unknown network declaration (exit 2)
OK resolve-role refuses duplicate role tool (exit 2)
OK resolve-role refuses unsupported tool (exit 2)
OK resolve-role refuses missing contract file (exit 4)
OK version 1 role prints no contract line
OK shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK resolve-role accepts a minimal version 2 role (exit 0)
OK resolve-role refuses an action outside the vocabulary (exit 2)
OK resolve-role refuses a gated-only action in a role file (exit 2)
OK resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK resolve-role refuses a contract that is a symbolic link (exit 2)
OK seat launches under role ceiling
OK role ceiling narrows seat tools (read,write,bash -> read,bash)
OK narrowing recorded loudly
OK missing role contract refuses launch (exit 2)
OK missing-contract refusal names the role
OK empty ceiling intersection -> tool-free seat
OK tool-free outcome recorded loudly
OK overridden agents dir without seat definition refuses (exit 4)
OK seat-resolution refusal names the seat
OK policy: mission only -> mission tools
OK policy: task only -> task tools
OK policy: both -> intersection (task narrowed)
OK policy: empty intersection -> tool-free
OK invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK task run with user layer present (exit 0)
OK user context dispatched into generated prompt
OK workspace+tools task runs via mock (exit 0)
OK workspace path + tools delivered to adapter
OK persistent workspace created on host
OK plain task still runs (no workspace/tools) (exit 0)
OK workspace var present but empty when absent
OK tools empty when absent
OK unknown tool exits 2 (exit 2)
OK workspace traversal exits 2 (exit 2)
OK live hello task succeeds with exact marker
OK result.json written in run dir
OK result.json contents are correct
OK wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK each run gets a distinct run dir (no clobber)
OK list shows both runs
OK fork base: teach succeeds (exit 0)
OK fork child recalls ancestor context (exit 0)
OK forked child recalled ancestor code word
OK ancestor session untouched by fork
OK child session dir has its own branch file
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
OK user recall run succeeds
OK recalled user name (response: Jason)
OK no agent identity on headless run
selftest: 98 passed, 0 failed
@@ -0,0 +1,3 @@
baseline: pass
default-skips-exec-check: SURVIVED
default-skips-char-check: SURVIVED
@@ -0,0 +1,28 @@
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1105.865823ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (348.121532ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (152.119187ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1140.613602ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (345.285064ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (125.690866ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (288.294263ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (271.346601ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (142.502119ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (287.744335ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (161.551667ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (413.883691ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (97.028519ms)
✔ bus stop refuses to signal a live pid that is not a bus host (218.667059ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (654.709284ms)
✔ bus start refuses with exit 3 without a notifier config (228.834369ms)
✔ bus start runs until bus stop; status reports it while it runs (1023.609058ms)
✔ bus-service.sh renders the unit and installs it into a given directory (214.273453ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (320.492752ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (50.120534ms)
ℹ tests 20
ℹ suites 0
ℹ pass 20
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 7812.181038
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r78-out/mutants-base/orig 2026-10-11 19:04:57.545751830 -0500
+++ scripts/bus-service.sh 2026-10-11 19:05:11.703448748 -0500
@@ -48,7 +48,7 @@
fi
fi
case "$bin" in /*) ;; *) die "claude path must be absolute: $bin" ;; esac
- case "$bin" in *[!A-Za-z0-9/._+-]*) die "claude path contains a character the unit cannot carry: $bin" ;; esac
+ case "$bin" in *[!A-Za-z0-9/._+-]*) [ "$CLAUDE_SET" = 0 ] || die "claude path contains a character the unit cannot carry: $bin" ;; esac
[ -f "$bin" ] && [ -x "$bin" ] || die "claude path is not an executable file: $bin"
echo "Environment=MOSAIC_CLAUDE_BIN=$bin"
}
@@ -0,0 +1,28 @@
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (850.771334ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (199.439311ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (108.361906ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1123.057482ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (190.859267ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (204.969281ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (163.106697ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (278.601659ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (108.947853ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (206.907826ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (129.306235ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (188.001447ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (37.12492ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.048436ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (332.162795ms)
✔ bus start refuses with exit 3 without a notifier config (132.940427ms)
✔ bus start runs until bus stop; status reports it while it runs (858.793061ms)
✔ bus-service.sh renders the unit and installs it into a given directory (57.885474ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (103.115441ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (18.69613ms)
ℹ tests 20
ℹ suites 0
ℹ pass 20
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5620.90404
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r78-out/mutants-base/orig 2026-10-11 19:04:57.545751830 -0500
+++ scripts/bus-service.sh 2026-10-11 19:05:05.750863894 -0500
@@ -49,7 +49,7 @@
fi
case "$bin" in /*) ;; *) die "claude path must be absolute: $bin" ;; esac
case "$bin" in *[!A-Za-z0-9/._+-]*) die "claude path contains a character the unit cannot carry: $bin" ;; esac
- [ -f "$bin" ] && [ -x "$bin" ] || die "claude path is not an executable file: $bin"
+ [ "$CLAUDE_SET" = 0 ] || { [ -f "$bin" ] && [ -x "$bin" ]; } || die "claude path is not an executable file: $bin"
echo "Environment=MOSAIC_CLAUDE_BIN=$bin"
}
@@ -0,0 +1,28 @@
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (996.499489ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (223.342261ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (121.843776ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1123.253024ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (219.282992ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (122.633803ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (185.710182ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (193.397498ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (105.502985ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (180.990346ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (111.662106ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (204.610299ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (34.123378ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.827134ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (392.30656ms)
✔ bus start refuses with exit 3 without a notifier config (142.595472ms)
✔ bus start runs until bus stop; status reports it while it runs (927.596381ms)
✔ bus-service.sh renders the unit and installs it into a given directory (85.864243ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (113.040738ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (32.921148ms)
ℹ tests 20
ℹ suites 0
ℹ pass 20
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5885.656683
@@ -0,0 +1,3 @@
baseline: pass
default-skips-exec-check: killed (✖ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space;)
default-skips-char-check: killed (✖ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space;)
@@ -0,0 +1,29 @@
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (888.215556ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (229.030916ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (124.839935ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1134.482209ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (199.337293ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (101.066858ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (185.590307ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (172.326478ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (95.951417ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (196.391959ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (103.307314ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (192.373787ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (31.198445ms)
✔ bus stop refuses to signal a live pid that is not a bus host (204.634318ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (335.412793ms)
✔ bus start refuses with exit 3 without a notifier config (140.618214ms)
✔ bus start runs until bus stop; status reports it while it runs (997.704612ms)
✔ bus-service.sh renders the unit and installs it into a given directory (64.082615ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (93.785576ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (18.466468ms)
✔ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (12.617617ms)
ℹ tests 21
ℹ suites 0
ℹ pass 21
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5648.06809
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r78-out/mutants-candidate/orig 2026-10-11 19:03:54.689088621 -0500
+++ scripts/bus-service.sh 2026-10-11 19:04:47.919911010 -0500
@@ -48,7 +48,7 @@
fi
fi
case "$bin" in /*) ;; *) die "claude path must be absolute: $bin" ;; esac
- case "$bin" in *[!A-Za-z0-9/._+-]*) die "claude path contains a character the unit cannot carry: $bin" ;; esac
+ case "$bin" in *[!A-Za-z0-9/._+-]*) [ "$CLAUDE_SET" = 0 ] || die "claude path contains a character the unit cannot carry: $bin" ;; esac
[ -f "$bin" ] && [ -x "$bin" ] || die "claude path is not an executable file: $bin"
echo "Environment=MOSAIC_CLAUDE_BIN=$bin"
}
@@ -0,0 +1,53 @@
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (894.978431ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (192.403647ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (99.462314ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1110.600482ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (255.958715ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (97.794802ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (189.015384ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (186.852112ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (110.575778ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (180.334025ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (100.937588ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (200.557688ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (29.89875ms)
✔ bus stop refuses to signal a live pid that is not a bus host (204.411386ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (344.977835ms)
✔ bus start refuses with exit 3 without a notifier config (140.003369ms)
✔ bus start runs until bus stop; status reports it while it runs (1100.345159ms)
✔ bus-service.sh renders the unit and installs it into a given directory (2180.711638ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (1419.951255ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (31.911761ms)
✖ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (22.022509ms)
ℹ tests 21
ℹ suites 0
ℹ pass 20
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 9264.641005
✖ failing tests:
test at packages/cli/tests/host.test.mjs:618:1
✖ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (22.022509ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r78/packages/cli/tests/host.test.mjs:647: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,11 @@
--- /mnt/storage/scratch/rocko-r78-out/mutants-candidate/orig 2026-10-11 19:03:54.689088621 -0500
+++ scripts/bus-service.sh 2026-10-11 19:04:42.385696149 -0500
@@ -49,7 +49,7 @@
fi
case "$bin" in /*) ;; *) die "claude path must be absolute: $bin" ;; esac
case "$bin" in *[!A-Za-z0-9/._+-]*) die "claude path contains a character the unit cannot carry: $bin" ;; esac
- [ -f "$bin" ] && [ -x "$bin" ] || die "claude path is not an executable file: $bin"
+ [ "$CLAUDE_SET" = 0 ] || { [ -f "$bin" ] && [ -x "$bin" ]; } || die "claude path is not an executable file: $bin"
echo "Environment=MOSAIC_CLAUDE_BIN=$bin"
}
@@ -0,0 +1,53 @@
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (875.635661ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (222.116078ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (109.443842ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1112.250834ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (205.23111ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (105.259436ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (174.767627ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (171.710496ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (116.920037ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (173.14447ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (107.972712ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (205.354979ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (38.66164ms)
✔ bus stop refuses to signal a live pid that is not a bus host (203.422065ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (329.314656ms)
✔ bus start refuses with exit 3 without a notifier config (134.162465ms)
✔ bus start runs until bus stop; status reports it while it runs (862.977011ms)
✔ bus-service.sh renders the unit and installs it into a given directory (85.718099ms)
✔ bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one (102.574975ms)
✔ bus-service.sh refuses a directory pin and keeps a symlink default unresolved (23.108485ms)
✖ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (9.751035ms)
ℹ tests 21
ℹ suites 0
ℹ pass 20
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 5485.0016
✖ failing tests:
test at packages/cli/tests/host.test.mjs:618:1
✖ bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space (9.751035ms)
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
0 !== 1
at TestContext.<anonymous> (file:///mnt/storage/scratch/rocko-r78/packages/cli/tests/host.test.mjs:640: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5)
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 0,
expected: 1,
operator: 'strictEqual',
diff: 'simple'
}
@@ -0,0 +1,32 @@
c6eb822c3cde8cc3ee1094c59c368994c0154fb17089ff74a6dbee59e1998de4 ./base.txt
a693ef0774c645f2186ef799dd7b51e528f388760f4cf2c7a4541e4e341ba6ef ./BUILD.md
ac65cb7ebf3139824f917f53466452ef660834375bcc05f3d518efb82c5cd718 ./build.patch
3016dbf19f495e1060aa6c3a13761231e1e2a1a8c4cefb28c592978d3b216b0f ./candidate-manifest.sha256
e074ad7bb3235dd33bad982f35ed0167e87f79e46a27b07dbae95c0e14dcfcc6 ./files.txt
c66ad9b1f058c19cce3e02083c6bf3b2122cc3d1897ea48c0f02c188c6b99c10 ./mutants.sh
41ad4f8f87419e94e8a09a52e0de2fe17872f686085418f0ef8be09ac14ddce8 ./out/gate/cli.log
c6e961ad82d95a1e80b7b2c911370c795c2fdc1ce18eba7b082b26518caf919d ./out/gate/end.txt
83636c9c3c26e7a4558f0684bc80d42fd6ce22f920cc859ee4e827d6d10d01b1 ./out/gate.sh
165ddd38b2d94eda9a86f5bf6a8666b5c8ce1090b2be280554affb21bb8450be ./out/gate/start.txt
4b666c77f5eeb8ea71ea357cb444dc0f1ce289baa72ec00789aed0daae57b185 ./out/gate/summary.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 ./out/gate/test-auth.log
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 ./out/gate/test-conductor.log
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 ./out/gate/test-config.log
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe ./out/gate/test-discord.log
ec5ff3a25b0cd345a8097a8f440bba76e1ae9c40dd12a965afdda64d893f7650 ./out/gate/test-extension-package.log
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f ./out/gate/test-foundation.log
c72bc84213ddaf63630388a99a9f70f4addfb532d7302d8819603d3d87be0b9d ./out/gate/test-queue.log
fa832794ee35025fb4559d58d01a270f6bff2eb65e863e9075b797f47fa68545 ./out/gate/test-release.log
ccea37f4ffde1f160dcd1a1864988083a645d974e892065c956b338d0f8c853a ./out/gate/test-task.log
a52eef1f353dc79359e222e98d2f307346e4bb7bb35177d3220f54fcf68f11ab ./out/mutants-base/baseline.log
29b6a0d51dd20b3823ffff41681b29115e3e2545b892464df90a5cada9f13d98 ./out/mutants-base/default-skips-char-check.diff
8db50f6080affa8f1434c180cce7552cf51f79c42612ad6f6de3a7e35380af61 ./out/mutants-base/default-skips-char-check.log
4fb49860e6c63ad3fde5b5c2a675560a943c89c0c784ff6642e049e87709e623 ./out/mutants-base/default-skips-exec-check.diff
fb4456735541f4c0e6543b5a26b60468c7d4911221db2db466a2fd936a97d0b8 ./out/mutants-base/default-skips-exec-check.log
ceacd2a03f3fd0a0f255690d26f7712654bb6cffe51ab119aa9fe69cd6015858 ./out/mutants-base.txt
1036cfc2cd133dfc706757c0b0ab2a3e82420f28be67b91a0a654793fc3f99be ./out/mutants-candidate/baseline.log
8d792f74aa51f49a4149ba3380ade269609f31bc1911e5592eb74ab3f6718af0 ./out/mutants-candidate/default-skips-char-check.diff
b426e7c8fffd455c22bbca11d9e886c5eb3d80918e4a02335dbb977152a17416 ./out/mutants-candidate/default-skips-char-check.log
b8ab87d48dff7b54ec1e25694313f24477f26a0e02da90872632ee13b346ff7c ./out/mutants-candidate/default-skips-exec-check.diff
8d64fc2f1e8725805a502fcd1f0d4ecc72a0e1bbbbd56185f5da7f71911bead8 ./out/mutants-candidate/default-skips-exec-check.log
06cb512d4d333bbff5895a51f18f625f55e92f29cd89a7ac4de4344aac1bc3f7 ./out/mutants-candidate.txt