diff --git a/agents/filbert/work/s4-follow-up-review/gate.sh b/agents/filbert/work/s4-follow-up-review/gate.sh index 2d619913..613a8227 100755 --- a/agents/filbert/work/s4-follow-up-review/gate.sh +++ b/agents/filbert/work/s4-follow-up-review/gate.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Sequential gate; $1 = tree, $2 = out prefix. Each output teed to a file. -T="$1"; P="$2"; O=~/filbert-scratch/r45/out; cd "$T" +# Sequential gate; $1 = tree, $2 = out prefix, $3 = out dir (optional). Each output teed to a file. +T="$1"; P="$2"; O="${3:-$HOME/filbert-scratch/r45/out}"; cd "$T" for d in packages/*/tests; do p=$(basename "$(dirname "$d")") env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-filbert-r45.sock timeout 900 node --test "packages/$p/tests/*.test.mjs" 2>&1 | tee "$O/$P-node-$p.txt" > /dev/null diff --git a/agents/filbert/work/s4-follow-up-review/mutants-r2.sh b/agents/filbert/work/s4-follow-up-review/mutants-r2.sh new file mode 100644 index 00000000..3d269893 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/mutants-r2.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Row 45 round 2 mutants (Filbert): Rocko's 31 (agents/rocko/work/s4-follow-up/mutants.sh, +# round 2), my round 1 X set that is not in his, and Y1-Y6 on the round 2 code. +# Same harness as mutants.sh, plus the wall time of each run, so a hang shows. +# Usage: mutants-r2.sh +set -u +T="$1"; O="$2"; cd "$T" +run() { + local id="$1" file="$2" expr="$3" s0 s1 + cp "$file" "$file.orig" + perl -0pi -e "$expr" "$file" + if cmp -s "$file" "$file.orig"; then echo "$id NOT-APPLIED $file"; mv "$file.orig" "$file"; return; fi + s0=$(date +%s) + env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-filbert-r45.sock timeout 900 node --test --test-timeout=90000 'packages/cli/tests/*.test.mjs' 'packages/discord/tests/*.test.mjs' > "$O/mut-$id.txt" 2>&1 + local rc=$? f c + s1=$(date +%s) + f=$(grep -E '^ℹ fail ' "$O/mut-$id.txt" | awk '{print $3}') + c=$(grep -E '^ℹ cancelled ' "$O/mut-$id.txt" | awk '{print $3}') + if [ "$rc" = 124 ]; then echo "$id killed (hang, outer timeout) $((s1 - s0)) s" + elif [ "${f:-?}" = 0 ] && [ "${c:-?}" = 0 ]; then echo "$id SURVIVED $((s1 - s0)) s" + else echo "$id killed (fail ${f:-?}, cancelled ${c:-?}) $((s1 - s0)) s"; fi + mv "$file.orig" "$file" +} +NT=packages/cli/src/notifier.mjs +HS=packages/cli/src/host.mjs +BS=scripts/bus-service.sh +# Rocko's 31, verbatim from his round 2 mutants.sh. +run N2 $NT 's/lstatSync, mkdirSync/lstatSync, statSync, mkdirSync/; s/const ds = lstatSync\(dir\);/const ds = statSync(dir);/' +run N4 $NT 's/O_WRONLY \| O_APPEND \| O_NOFOLLOW\)/O_WRONLY | O_APPEND)/' +run N5 $HS 's/if \(child\.exitCode !== null \|\| child\.signalCode !== null\) onDeath/if (child.exitCode !== null) onDeath/' +run M28 $HS 's/ if \(prior\?\.live\) throw new CliError\([^\n]*\n//' +run G150 $HS 's/(if \(ok\?\.ok !== true\) \{\n\s*await ended\(notify, 5000\);\n\s*)if \(broker\.connected\) /$1/' +run G144 $HS 's/(notify\.kill\("SIGTERM"\);\n\s*await ended\(notify, 5000\);\n\s*)if \(broker\.connected\) /$1/' +run F2a $NT 's/export const REFUSAL_LIMIT = 5;/export const REFUSAL_LIMIT = 6;/' +run F2b $NT 's/ && r\.status !== 429;/;/' +run F2c $NT 's/ \|\| journal\.gaveUp\.has\(d\.id\)\) continue;/) continue;/' +run F2d $NT 's/if \(\(journal\.refusals\.get\(d\.id\) \?\? 0\) >= REFUSAL_LIMIT\) \{/if (false) {/' +run F2e $NT 's/ : dmGaveUp\(d\.id\) \? "\[blocking, DM refused, not retried\] "//' +run F2f $NT 's/ if \(record\.kind === "dm" && \(journal\.refusals\.get\(record\.decision\) \?\? 0\) >= REFUSAL_LIMIT\) \{/ if (false) {/' +run J4a $NT 's/ \|\| new Date\(r\.at\)\.toISOString\(\) !== r\.at\) return "at";/) return "at";/' +run J4b $NT 's/typeof r\.decision !== "string" \|\| r\.decision === ""/false/' +run J4c $NT 's/if \(r\.status !== undefined && !Number\.isInteger\(r\.status\)\) return "status";//' +run J4d $NT 's/r\.outcome === "confirmed" \? typeof r\.messageId !== "string" : //' +run E1 $NT 's/accessSync\(dir, constants\.W_OK \| constants\.X_OK\);//' +run E2 $NT 's/ if \(ds\.isSymbolicLink\(\)\) throw[^\n]*\n//' +run G144cb $HS 's/(notify\.kill\("SIGTERM"\);\n\s*await ended\(notify, 5000\);\n\s*if \(broker\.connected\) broker\.send\(\{ op: "close" \}), \(\) => \{\}\)/$1)/' +run G150cb $HS 's/(if \(ok\?\.ok !== true\) \{\n\s*await ended\(notify, 5000\);\n\s*if \(broker\.connected\) broker\.send\(\{ op: "close" \}), \(\) => \{\}\)/$1)/' +run G184 $HS 's/notify\.send\(\{ op: "stop" \}, \(\) => \{\}\)/notify.send({ op: "stop" })/' +run G188 $HS 's/(await queue;\n\s*if \(broker\.connected\) broker\.send\(\{ op: "close" \}), \(\) => \{\}\)/$1)/' +run R2a $NT 's/const wait = definite\(line\) \? BACKOFF_MAX_MS : /const wait = /' +run R2b $NT 's/ if \(t\.getTime\(\) < \(journal\.refusedAt\.get\(d\.id\)[^\n]*\n//' +run R2c $NT 's/ refusedAt\.set\(r\.decision, Date\.parse\(r\.at\)\);\n//' +run X9 $NT 's/ if \(UNWRITABLE\.includes\(e\.code\)\) throw new CliError\(`notify journal is not writable[^\n]*\n//' +run X14 $BS 's/ mkdir -m 0700 -p \/notify\/[^\n]*\n//' +run D3 $NT 's/ && !Number\.isNaN\(Date\.parse\(d\)\) && new Date\(d\)\.toISOString\(\)\.startsWith\(d\)//' +run N1a $NT 's/ if \(e\.code === "ENOENT" && lstatSync\(dir[^\n]*\n//' +run N1b $NT 's/\[\.\.\.UNWRITABLE, "ENOTDIR"\]/UNWRITABLE/' +run N1c $NT 's/ if \(e\.code === "EISDIR"\)[^\n]*\n//' +# My round 1 set, where it still applies and Rocko's set has no twin. +run X1 $NT 's/r\.outcome === "refused" && Number\.isInteger\(r\.status\) && r\.status >= 400 && r\.status < 500 && r\.status !== 429;/r.outcome === "refused";/' +run X2 $NT 's/r\.status >= 400 && r\.status < 500 &&/r.status >= 400 \&\& r.status < 600 \&\&/' +run X3 $NT 's/ if \(bad\) throw new CliError\(`notify journal line[^\n]*\n count\(r\);/$&\n if (definite(r)) refusals.delete(r.decision);/' +run X4 $NT 's/r\.kind === "digest" \? !isDay\(r\.day\) :/r.kind === "digest" ? false :/' +run X5 $NT 's/ \|\| \(r\.outcome === "gave-up" && r\.kind !== "dm"\)\) return "outcome";/) return "outcome";/' +run X6 $NT 's/ if \(\[\.\.\.UNWRITABLE, "ENOTDIR"\]\.includes\(e\.code\)\) throw new CliError\(`notify journal directory cannot be created[^\n]*\n//' +run X7 $NT 's/ : r\.decision !== null && r\.decision !== undefined\) return "decision";/ : false) return "decision";/' +run X8 $NT 's/ if \(e\.code === "ELOOP"\) throw new CliError\(`notify journal must not be a symlink[^\n]*\n//' +run X10 $NT 's/: r\.messageId !== null && typeof r\.messageId !== "string"\) return "messageId";/: false) return "messageId";/' +run X11 $NT 's/if \(!\["dm", "digest"\]\.includes\(r\.kind\)\) return "kind";//' +run X12 $NT 's/if \(r\.kind === "dm" && r\.outcome === "gave-up"\) gaveUp\.add\(r\.decision\);/if (false) gaveUp.add(r.decision);/' +run X13 $NT 's/(function giveUp\(decision, t\) \{\n\s*)journal\.append\(/$1if (0) journal.append(/' +# Round 2 additions. +run Y1 $NT 's/ refusedAt\.set\(r\.decision, Date\.parse\(r\.at\)\);/ if (!refusedAt.has(r.decision)) refusedAt.set(r.decision, Date.parse(r.at));/' +run Y2 $NT 's/const wait = definite\(line\) \? BACKOFF_MAX_MS : Math\.min\(BACKOFF_MS \* 2 \*\* n, BACKOFF_MAX_MS\);/const wait = BACKOFF_MAX_MS;/' +run Y3 $NT 's/(journal\.refusedAt\.get\(d\.id\) \?\? -Infinity\) \+ )BACKOFF_MAX_MS\) continue;/$1BACKOFF_MS) continue;/' +run Y4 $NT 's/if \(t\.getTime\(\) < \(journal\.refusedAt/if (t.getTime() <= (journal.refusedAt/' +run Y5 $NT 's/const definite = \(r\) => r\.kind === "dm" && r\.outcome === "refused"/const definite = (r) => r.outcome === "refused"/' +run Y6 $NT 's/ && !Number\.isNaN\(Date\.parse\(d\)\) && new Date/ \&\& new Date/' diff --git a/agents/filbert/work/s4-follow-up-review/probe/append-check.test.mjs b/agents/filbert/work/s4-follow-up-review/probe/append-check.test.mjs new file mode 100644 index 00000000..dfe94361 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/probe/append-check.test.mjs @@ -0,0 +1,20 @@ +// Row 45 round 2 (Filbert): what a type check inside append would do. Run +// against a tree whose append calls badField first (a temporary edit). +import { test } from "node:test"; +import { existsSync, readFileSync } from "node:fs"; +const T = process.env.TREE; +const { createNotifier, journalPath, POLL_MS } = await import(`${T}/packages/cli/src/notifier.mjs`); +const { broker, tmp } = await import(`${T}/packages/cli/tests/helpers.mjs`); +test("A3 append type check with a numeric messageId from Discord", async (t) => { + const bus = broker(t); + const dataRoot = tmp(t); + const clock = { t: new Date("2026-10-08T05:00:00Z") }; + let sends = 0; + const direct = { async send() { sends++; return { messageId: 123 }; } }; + const n = createNotifier({ business: "demo", dataRoot, inbox: async () => bus.read("inbox"), direct, now: () => clock.t, log: () => {} }); + bus.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); + for (let i = 0; i < 20; i++) { await n.tick().catch(() => {}); clock.t = new Date(clock.t.getTime() + POLL_MS); } + const f = journalPath(dataRoot, "demo"); + const lines = existsSync(f) ? readFileSync(f, "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l).outcome) : []; + console.log("PROBE A3 DMs actually sent in 10 min:", sends, "journal:", lines.join(",")); +}); diff --git a/agents/filbert/work/s4-follow-up-review/probe/probe-r2.test.mjs b/agents/filbert/work/s4-follow-up-review/probe/probe-r2.test.mjs new file mode 100644 index 00000000..9a1b3f6b --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/probe/probe-r2.test.mjs @@ -0,0 +1,73 @@ +// Row 45 round 2 probes (Filbert). Run: node --test probe-r2.test.mjs, with TREE set. +// probe.test.mjs (round 1) is rerun unchanged next to this. +import { test } from "node:test"; +import { appendFileSync, existsSync, readFileSync } from "node:fs"; +const T = process.env.TREE; +const { createNotifier, journalPath, openJournal, POLL_MS } = await import(`${T}/packages/cli/src/notifier.mjs`); +const { RestOutcome } = await import(`${T}/packages/discord/src/rest.mjs`); +const { broker, tmp } = await import(`${T}/packages/cli/tests/helpers.mjs`); + +const log = (...a) => console.log("PROBE", ...a); + +function rig(t, iso, answer) { + const bus = broker(t); + const dataRoot = tmp(t); + const clock = { t: new Date(iso) }; + const sends = []; + const logs = []; + const direct = { async send(m) { sends.push({ at: clock.t.toISOString(), nonce: m.nonce }); return answer(clock.t); } }; + const make = () => createNotifier({ business: "demo", dataRoot, inbox: async () => bus.read("inbox"), direct, now: () => clock.t, log: (l) => logs.push(l) }); + const journal = () => !existsSync(journalPath(dataRoot, "demo")) ? [] : readFileSync(journalPath(dataRoot, "demo"), "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)); + return { ...bus, dataRoot, clock, sends, logs, make, journal }; +} +const refuse403 = () => { throw new RestOutcome("refused", "dm: refused", { status: 403 }); }; +const raise = (s) => s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); +const mins = (s, start) => s.sends.map((x) => (Date.parse(x.at) - start) / 60000); + +// A host in a crash loop: a fresh notifier every 15 s (RestartSec), one tick each. +test("R2-T6 crash loop against a permanent 403: restart every 15 s", async (t) => { + const s = rig(t, "2026-10-08T05:00:00Z", refuse403); + raise(s); + const start = s.clock.t.getTime(); + for (let i = 0; i < 4 * 60 * 4 && !s.journal().some((r) => r.outcome === "gave-up"); i++) { + await s.make().tick(); + s.clock.t = new Date(s.clock.t.getTime() + 15_000); + } + const gave = s.journal().find((r) => r.outcome === "gave-up"); + log("T6 sends at min", mins(s, start).join(" "), "gave-up at min", gave ? (Date.parse(gave.at) - start) / 60000 : "none"); +}); + +// The binding is fixed at 100 min: does the DM land before the limit? +test("R2-T7 binding fixed at 100 min", async (t) => { + const t0 = Date.parse("2026-10-08T05:00:00Z"); + const s = rig(t, "2026-10-08T05:00:00Z", (now) => (now.getTime() - t0 < 100 * 60_000 ? refuse403() : { messageId: "123456789012345678" })); + raise(s); + const n = s.make(); + for (let i = 0; i < 4 * 120; i++) { await n.tick(); s.clock.t = new Date(s.clock.t.getTime() + POLL_MS); } + log("T7 outcomes", s.journal().filter((r) => r.kind === "dm").map((r) => r.outcome).join(","), "sends at min", mins(s, t0).join(" ")); +}); + +// The wall clock steps back an hour after a refusal (or a line was written +// with a clock an hour fast): the DM waits for the line's own time plus 30 min. +test("R2-T8 a refusal line an hour in the future holds the DM", async (t) => { + const s = rig(t, "2026-10-08T05:00:00Z", () => ({ messageId: "123456789012345678" })); + const d = raise(s); + const file = journalPath(s.dataRoot, "demo"); + openJournal(file); + appendFileSync(file, `${JSON.stringify({ at: "2026-10-08T06:00:00.000Z", kind: "dm", decision: d.id, outcome: "refused", messageId: null, status: 403 })}\n`); + const n = s.make(); + const start = s.clock.t.getTime(); + for (let i = 0; i < 4 * 120 && s.sends.length === 0; i++) { await n.tick(); s.clock.t = new Date(s.clock.t.getTime() + POLL_MS); } + log("T8 first send at min", mins(s, start)[0] ?? "none", "log lines", s.logs.length); +}); + +// A 401 (bad token) is definite too: same pace as a 403. +test("R2-T9 a permanent 401", async (t) => { + const s = rig(t, "2026-10-08T05:00:00Z", () => { throw new RestOutcome("refused", "dm: refused", { status: 401 }); }); + raise(s); + const n = s.make(); + const start = s.clock.t.getTime(); + for (let i = 0; i < 4 * 120 && !s.journal().some((r) => r.outcome === "gave-up"); i++) { await n.tick(); s.clock.t = new Date(s.clock.t.getTime() + POLL_MS); } + const gave = s.journal().find((r) => r.outcome === "gave-up"); + log("T9 sends at min", mins(s, start).join(" "), "gave-up at min", gave ? (Date.parse(gave.at) - start) / 60000 : "none"); +}); diff --git a/agents/filbert/work/s4-follow-up-review/r2-append-check.txt b/agents/filbert/work/s4-follow-up-review/r2-append-check.txt new file mode 100644 index 00000000..fcb58ea5 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-append-check.txt @@ -0,0 +1,10 @@ +PROBE A3 DMs actually sent in 10 min: 20 journal: unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown +✔ A3 append type check with a numeric messageId from Discord (29.135612ms) +ℹ tests 1 +ℹ suites 0 +ℹ pass 1 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 88.225738 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-bus.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-bus.txt new file mode 100644 index 00000000..cc608edf --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-bus.txt @@ -0,0 +1,75 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (22.269589ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (26.323243ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (23.785686ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (15.206095ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (15.919968ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (14.105447ms) +✔ task action subjects and linked decision trail are complete and ordered (20.672808ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (10.328679ms) +✔ business isolation includes inherited object names and cross-business message references (15.613693ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (19.54884ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (11.360551ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (19.673341ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (11.566139ms) +✔ both arbiters require human resolution when their cross-role route is themselves (16.369231ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.716038ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.420646ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (3.396523ms) +✔ expiry refuses use and env references never become client data (0.895178ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.971315ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.451643ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.096655ms) +✔ process reader gets own kernel identity without exposing environment values (1.409154ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.116193ms) +✔ real pid 1 remains inspectable when its environment is protected (0.393851ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (29.47045ms) +✔ missing and foreign-business citations refuse and roll back message and grant (18.027887ms) +✔ cross-role sends still need a matching resolved decision and consume it once (22.402467ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (73.960115ms) +✔ startup token refusal returns safe code without value or partial listening broker (43.678689ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (53.388918ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (96.86117ms) +✔ trusted host registers later launches; socket clients never have a registration verb (49.893538ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (37.622809ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (50.223788ms) +✔ v3b prototype refusals, views and append-only mutations (78.810105ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (28.023209ms) +✔ another run cannot consume an approval; a failed check leaves it usable (19.669819ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (16.620371ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (29.070385ms) +✔ class drift gated to cross-role refuses before consumption (19.941225ms) +✔ class drift cross-role to gated refuses before consumption (15.681301ms) +✔ class drift gated to within-role refuses before consumption (14.53125ms) +✔ class drift cross-role to within-role refuses before consumption (19.114557ms) +✔ class drift within-role to gated refuses before consumption (16.518803ms) +✔ class drift within-role to cross-role refuses before consumption (17.955461ms) +✔ message.send consumes approval and prevents a later send or authorize (19.488741ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (18.191127ms) +✔ creates private WAL store and excludes a second writer until explicit close (16.936863ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (16.623382ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (16.767184ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (47.158115ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (10.346432ms) +✔ async transactions refuse before invoking their function (5.914295ms) +✔ recordTask keeps sync reads and a role write apart (22.489595ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (8.434197ms) +✔ a bad entry refuses the whole record (10.470129ms) +✔ taskView reads the projection for one business (13.621079ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (21.32184ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (271.546348ms) +✔ without an adapter the server refuses every task verb (15.491437ms) +✔ the runtime refuses an invalid adapter and closes a valid one (14.948059ms) +✔ the process loads the S3 adapter from plain-data trackers (90.984736ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (25.840025ms) +✔ two wire claims serialize; a lost reply never automatically retries (20.046608ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (12.058937ms) +✔ client preserves UTF-8 when a response divides a multibyte character (11.621129ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (15.405652ms) +ℹ tests 67 +ℹ suites 0 +ℹ pass 67 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 578.464195 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-business.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-business.txt new file mode 100644 index 00000000..9dad12e8 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-business.txt @@ -0,0 +1,68 @@ +✔ config directory and file path follow MOSAIC_CONFIG (1.462281ms) +✔ the fixture business validates and comes back frozen (5.475908ms) +✔ two instances may share a definition (2.129858ms) +✔ top-level refusals (6.639467ms) +✔ arbiters and projects (9.019451ms) +✔ role instances (3.858786ms) +✔ Vikunja bots (7.746733ms) +✔ a role without Vikunja takes no tracker block (2.324392ms) +✔ credential references match the definition's services (2.605395ms) +✔ launch (9.417556ms) +✔ loadBusiness: file checks (2.084041ms) +✔ loadBusiness: not a regular file (45.339895ms) +✔ loading writes nothing (1.300868ms) +✔ names that are Object.prototype properties don't count as declared (2.539134ms) +✔ the shipped example refuses as written and validates once filled in (1.056039ms) +✔ usage errors exit 4 (288.474146ms) +✔ validate: a good business exits 0 and prints instance digests (69.678969ms) +✔ validate: project files (320.074457ms) +✔ validate: missing files and a broken system config (240.87772ms) +✔ validate: credential reference problems exit 2 and name each one (62.786436ms) +✔ validate: a token file inside the repository is refused (64.295881ms) +✔ validate: role definitions come from MOSAIC_ROLES_DIR (190.030585ms) +✔ resolve: prints one instance's record (205.027547ms) +✔ resolve: refusals (385.503524ms) +✔ parse: exactly one of file or env, plus the service's date (2.952072ms) +✔ check: a good file has no problems (0.824556ms) +✔ check never opens the file: a write-only token passes (0.353532ms) +✔ check: file problems (0.999572ms) +✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.322104ms) +✔ check: dates and environment references (0.514167ms) +✔ path and load (3.02212ms) +✔ refusals (1.710183ms) +✔ systemVars flattens the validated config (2.515066ms) +✔ precedence: system, business, project, project role, agent (6.259725ms) +✔ limits narrow the definition and never widen it (2.937512ms) +✔ role.launch stays within-role only for the instance the launch block names (5.533469ms) +✔ limits.authority without role.launch leaves the launcher with no launch block (1.918708ms) +✔ limits.authority narrows cross-role actions too (1.299526ms) +✔ classify (1.144947ms) +✔ the record carries what the broker and launcher need (0.967031ms) +✔ digest: key order doesn't matter, any value change does (6.010252ms) +✔ refusals (2.707932ms) +✔ the four shipped version 2 roles load (3.835491ms) +✔ shipped role scopes match addendum B section 2 and the SR runbook (1.431581ms) +✔ shipped authority follows the note's table (0.847995ms) +✔ version 1 files keep loading with no authority (1.308957ms) +✔ the conductor policy isn't a role (0.294164ms) +✔ a missing role file is exit 4, a symbolic link too (0.542306ms) +✔ version 2 refusals (1.569867ms) +✔ authority: closed vocabulary, no gated-only action, no overlap (2.152725ms) +✔ credentials: Gitea scopes (0.922485ms) +✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.10253ms) +✔ credentials: services (0.621578ms) +✔ contract: a non-empty regular Markdown file beside the role file (0.721025ms) +✔ every key names known layers and a merge rule (1.324228ms) +✔ unknown keys and wrong layers refuse (1.159511ms) +✔ types (2.057378ms) +✔ merge: defaults, then the most specific layer wins (0.354038ms) +✔ merge: limits only narrow, and provenance lists each source (0.399206ms) +✔ merge doesn't change its inputs (0.169744ms) +ℹ tests 60 +ℹ suites 0 +ℹ pass 60 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 1908.093348 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-cli.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-cli.txt new file mode 100644 index 00000000..e9ee7049 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-cli.txt @@ -0,0 +1,76 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (17.756486ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (20.372711ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (13.26815ms) +✔ decide prints a declining choice as declining (12.55307ms) +✔ an unknown outcome is reported once and never resent (11.912024ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (12.139972ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (10.474322ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (12.94795ms) +✔ every human command refuses inside an agent run before it touches the bus (9.939228ms) +✔ usage errors exit 4; no business and no host is a usage error (8.769361ms) +✔ agents and tasks print through the broker (8.804697ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.24077ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (41.26972ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (36.744915ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (40.410485ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (35.375393ms) +✔ a business without tracker.baseUrl gets no trackers entry (37.067769ms) +✔ an unknown business and a broken system config refuse with exit 3 (64.214464ms) +✔ empty views say so (1.077472ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.056129ms) +✔ tasks print the tracker fields the snapshot carries (0.152213ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (859.51201ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (133.970869ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (93.960247ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (151.415451ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (124.799643ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.045482ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (130.064799ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (67.272914ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (132.902054ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (20.710531ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.124304ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (205.681466ms) +✔ bus start refuses with exit 3 without a notifier config (85.490082ms) +✔ bus start runs until bus stop; status reports it while it runs (603.373838ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.649614ms) +✔ zoned uses the IANA zone across DST (17.338983ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (16.286929ms) +✔ two blocking decisions get two DMs with different nonces (13.393741ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.216386ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (11.757739ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (16.310351ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (20.397817ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (10.759943ms) +✔ 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 (87.330481ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (46.678036ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (13.762904ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (8.57005ms) +✔ an inbox read failure is logged and the next poll retries (0.61166ms) +✔ no Discord id reaches the journal or the log (7.870988ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.777778ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.223151ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.186201ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.392517ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.503263ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.704798ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.303916ms) +✔ 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.435351ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.428697ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.417491ms) +✔ digest content stays within Discord's 2000 characters (0.244655ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.670674ms) +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 (266.656907ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (35.795671ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2157.22072ms) +✔ busExit and refuseInsideAgent (0.42461ms) +ℹ tests 66 +ℹ suites 0 +ℹ pass 66 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3009.134566 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-control-board.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-control-board.txt new file mode 100644 index 00000000..654b9230 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-control-board.txt @@ -0,0 +1,132 @@ +✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (4.135827ms) +✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.210436ms) +✔ completed smoke replies and ordinary questions are idle, not human blockers (0.699414ms) +✔ only an explicit first-line input request makes a finished reply waiting (0.148143ms) +✔ tool activity, user text, errors and unfinished turns override attention text (0.147832ms) +✔ STOP access failure is unknown, not absence, under a non-root identity (37.787009ms) +✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.106107ms) +✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.4334ms) +✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (5.361707ms) +✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.521775ms) +✔ server rescans connector discovery and refuses HTTP reply without transport (32.278821ms) +✔ connector session links and linked directories are not read (1.000678ms) +✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.412601ms) +✔ CLI print uses the relaunch notice instead of old current preview (62.390551ms) +✔ connector owner and fixed task never inherit a native relaunch notice (1.954624ms) +✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.084418ms) +✔ loadConfig: missing file throws ConfigError (1.343455ms) +✔ loadConfig: invalid JSON throws ConfigError (0.283883ms) +✔ loadConfig: missing dataRoot throws ConfigError (0.20708ms) +✔ loadConfig: relative dataRoot throws ConfigError (0.265955ms) +✔ loadConfig: valid config returns dataRoot (0.702357ms) +✔ findNewestSession: picks the newest by mtime among two files (0.444038ms) +✔ findNewestSession: finds files in nested subdirectories (0.267473ms) +✔ findNewestSession: returns null for a missing dir (0.114774ms) +✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.61014ms) +✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.165115ms) +✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.452552ms) +✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.02783ms) +✔ deriveState: full state table (0.161564ms) +✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.341687ms) +✔ rule: newest entry is a tool result with no assistant text after it is working (0.288665ms) +✔ rule: a finished ordinary turn is idle, even if it says your move (0.271276ms) +✔ task: the first user message of the session, from text blocks (0.364553ms) +✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.280905ms) +✔ task: no user message in the log means null (shown as unknown), never a guess (0.276908ms) +✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.388754ms) +✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.507815ms) +✔ scan: the written record carries task, workspace and activeProject (0.456049ms) +✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.612128ms) +✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.377951ms) +✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (0.982272ms) +✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.410222ms) +✔ loadRegistrations: a missing seatsDir gives empty lists (0.164391ms) +✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.792023ms) +✔ matchRegistration: matches by sessionsDir, and by realpath through a symlink; sessionsDir null never matches; same seat name with a different sessionsDir does not match (fleet vs repo darkwing) (0.342925ms) +✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.687664ms) +✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.666522ms) +✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.472962ms) +✔ scanAgent: ageSeconds is computed from the injected now (0.248196ms) +✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.172623ms) +✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.314731ms) +✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.449953ms) +✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.901505ms) +✔ scan: relative boardDir throws ConfigError (0.095605ms) +✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (64.80247ms) +✔ CLI: missing config exits 2 with a refused: message (51.46176ms) +✔ CLI: unknown command exits 2 (55.221738ms) +✔ CLI: unknown --liveness value exits 2 (54.127162ms) +✔ panesRunPi: true when any trimmed line equals 'pi' (0.198ms) +✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.075594ms) +✔ tmuxIsAlive: a pane running pi is alive (0.195581ms) +✔ tmuxIsAlive: session exists but pi has exited is not alive (0.072822ms) +✔ tmuxIsAlive: no such tmux session is not alive (0.079007ms) +✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.073417ms) +✔ tmuxIsAlive: passes -L only when a socket is given (0.118762ms) +✔ parsePanes: one pane per line, command and optional tab-separated path (0.083285ms) +✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.092461ms) +✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.109264ms) +✔ loadSeen: missing file returns {} (0.169756ms) +✔ loadSeen: invalid JSON throws ConfigError (0.212335ms) +✔ loadSeen: a JSON array throws ConfigError (0.203578ms) +✔ loadSeen: a non-string value throws ConfigError (0.171622ms) +✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.326161ms) +✔ markSeen: seen false deletes the key (0.287373ms) +✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.223736ms) +✔ markSeen: project containing '/' throws ConfigError (0.128066ms) +✔ markSeen: non-boolean seen throws ConfigError (0.114169ms) +✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.297554ms) +✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.289774ms) +✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.229979ms) +✔ scanAgent: an error-state session with a matching mark is seen (0.229443ms) +✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.498427ms) +✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.186489ms) +✔ taskSetBy: a registered task carries the record's setter; a record without the field (pre-#1511) reads unknown; the value is not copied into registered (0.591341ms) +✔ taskSetBy: null whenever the task shown is not the registered one: no registration, an empty registered task, a stale registration; the field is always present (0.575327ms) +✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (1.113299ms) +✔ isLoopbackHost: recognizes loopback hosts (1.270374ms) +✔ isLoopbackHost: rejects non-loopback hosts (4.563036ms) +✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.325436ms) +✔ startServer: serves page, healthz, and a rescanning /api/board (35.787906ms) +✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (8.973308ms) +✔ startServer: /api/board returns 500 JSON with an error field when scan throws (3.228814ms) +✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (54.811829ms) +✔ CLI: serve rejects a non-numeric --port with exit 2 (54.410067ms) +✔ CLI: scan still works after the async cli refactor (56.700877ms) +✔ CLI: live serve prints its URL and answers /healthz (59.861065ms) +✔ page.html: esc() escapes every HTML-significant character (0.575436ms) +✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (7.979697ms) +✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (1.840198ms) +✔ POST /api/seen with invalid JSON returns 400 (2.710894ms) +✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (1.962452ms) +✔ POST /api/seen with a missing agent returns 400 (1.244614ms) +✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.622494ms) +✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (49.558403ms) +✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.296985ms) +✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.261175ms) +✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.182485ms) +✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.146255ms) +✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.31441ms) +✔ page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped (0.548932ms) +✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S :control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (28.792892ms) +✔ POST /api/reply: a registration with a tmux socket adds -L (25.586572ms) +✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (27.723543ms) +✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (13.867125ms) +✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (3.579248ms) +✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.166628ms) +✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (0.612461ms) +✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (4.498148ms) +✔ page.html: the task cell and detail show who set a registered task via setByTag()/setByText(), both escaped, only from rec.taskSetBy; the reply gate does not read it (0.331382ms) +✔ Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers (7.966298ms) +✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (29.812163ms) +✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (5.457856ms) +✔ every refusal code the reader can raise has an HTTP status (0.918055ms) +✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (46.316154ms) +ℹ tests 124 +ℹ suites 0 +ℹ pass 124 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 631.116802 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-conversation.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-conversation.txt new file mode 100644 index 00000000..d152262d --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-conversation.txt @@ -0,0 +1,160 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (154.423664ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (8.3617ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (4.058303ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (239.259428ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (224.928554ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (163.055488ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (24.005309ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (25.383498ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.750918ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5605.707049ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (21376.118266ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (151.109397ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (97.512181ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (224.866555ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (186.12ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (209.370737ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (32.427494ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (102.751264ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (32.41744ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (106.122509ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (24.737685ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (60.110727ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (58.057822ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.561313ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.166637ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.770575ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2570.608867ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (142.523032ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2483.902016ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4296.662171ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2150.655232ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2241.039102ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2155.96549ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4356.673246ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (398.270607ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (356.042252ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (251.099722ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (60.883705ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (32.913555ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (48.285668ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3026.239393ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (4.78255ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (32.305845ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (26.643471ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (40.741903ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (29.567329ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (21.835822ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (49.499749ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (23.318307ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.569519ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (20.269936ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (124.10637ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (51.101259ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (30.389478ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (45.426157ms) +✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (45.532115ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (12.033502ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (32.003806ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (50.572921ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (41.213986ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (22.879567ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.502853ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.40385ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.528725ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.199267ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (389.071653ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (52.913248ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (94.847687ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (174.310087ms) +✔ H4: self-takeover is refused (22.518276ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (111.17958ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (97.896073ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (24.846537ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (82.035179ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (131.763377ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (21.153254ms) +✔ H13: a retry with the same request ID and different text is refused (17.236695ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (138.318859ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (70.743619ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (18.189836ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (69.420526ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (61.370549ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (14.839811ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (119.250803ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.769615ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (467.420133ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (362.402384ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (324.722375ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2368.214241ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (467.045823ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (10.105772ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (3.071788ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.851271ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (4.267968ms) +✔ F1: an unreadable fork is never merged into another branch's history (3.028409ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.619601ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.940326ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.417406ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.760353ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.801964ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.30304ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (7.962567ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (8.307486ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.155873ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (2.38788ms) +✔ F9: registrations never add or redirect a root (1.83449ms) +✔ F10: a header cwd naming another project is refused (3.784063ms) +✔ F11: parentSession renders with a marker and the parent is never opened (1.083441ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (6.364709ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.948402ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.936134ms) +✔ F13: compaction is a marker in place, then the retained content (1.141009ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (766.075473ms) +✔ fragments never cut a surrogate pair and keep an empty string (6.866327ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (2.242808ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.742956ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.34162ms) +✔ a seat directory without search permission refuses that root, not the catalogue (2.726249ms) +✔ every page and cursor is a valid CHAT-01 record (912.789881ms) +✔ the engine pin holds for the installed package (3.36609ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (433.851056ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (337.080877ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (92.100444ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (68.361827ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (22.945491ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.784552ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (32.59726ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (27.343118ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (128.501677ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (66.254844ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1546.560044ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (15.090901ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (70.573498ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (15.094525ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (17.271779ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (33.020097ms) +✔ N10: fake conformance (33.060086ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (30.209583ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (19.201868ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (65.052681ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (29.080497ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (28.783463ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (20.952204ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (14.690715ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (26.260079ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (109.255973ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (135.25188ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (86.825084ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (17.143713ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (16.70917ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (13.899703ms) +✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (42.691573ms) +ℹ tests 152 +ℹ suites 0 +ℹ pass 152 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 31139.736981 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-discord.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-discord.txt new file mode 100644 index 00000000..3445e738 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-discord.txt @@ -0,0 +1,186 @@ +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.496957ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.883819ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (1.262686ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.570463ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (21.843769ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.818429ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.555335ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.457364ms) +✔ authorize: open channel, listed user (1.968701ms) +✔ authorize: wrong guild (0.329705ms) +✔ authorize: no guild (DM) (0.250693ms) +✔ authorize: unlisted channel (0.193159ms) +✔ authorize: unknown channel, no info (0.261901ms) +✔ authorize: thread of listed parent (0.254159ms) +✔ authorize: thread of unlisted parent (0.260656ms) +✔ authorize: text channel that is not a thread and not listed (0.157899ms) +✔ authorize: unlisted user (1.066985ms) +✔ authorize: no author (0.265811ms) +✔ authorize: bot author (listed id, bot flag) (0.159841ms) +✔ authorize: system author (0.124289ms) +✔ authorize: the bot itself (0.178013ms) +✔ authorize: webhook (0.656771ms) +✔ authorize: mention channel without mention (0.160886ms) +✔ authorize: mention channel with bot mention (0.144012ms) +✔ authorize: mention channel with @everyone only (0.270319ms) +✔ authorize: mention channel mentioning someone else (0.11313ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.090947ms) +✔ authorize: private thread under mention channel, mentioned (0.085116ms) +✔ authorize: private thread under mention channel, not mentioned (0.075301ms) +✔ authorize: thread in another guild per channel info (0.075654ms) +✔ authorize: not an object (0.066592ms) +✔ authorize: no id (0.064422ms) +✔ authorize: oversize content is accepted and flagged (0.070714ms) +✔ authorize: exactly the limit is not oversize (0.064629ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.505887ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.157608ms) +✔ binding: a complete binding validates and is frozen (2.791903ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.334711ms) +✔ binding: empty allowlists refuse (1.276069ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.329081ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.018186ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.276973ms) +✔ binding: file must be 0600, regular, not a symlink (2.525517ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.731557ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (103.971133ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.195862ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (321.155641ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (212.294228ms) +✔ cli: run refuses when STOP is present, before any network use (141.315492ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.749962ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.17361ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (21.74909ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.975033ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.500525ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.299875ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.710261ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.511153ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.726962ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.735817ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.962023ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.3505ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.927767ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.512919ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.284738ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.22414ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.478552ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.246525ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.543684ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.501033ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (2.628311ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.113853ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.287727ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.255753ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.712659ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.889178ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.867872ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.934633ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.221875ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.568002ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.248962ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.667626ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.714399ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.421326ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (55.832881ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (39.499921ms) +✔ engine: one prompt, one turn, text and usage come back (30.785632ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (333.885017ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.643431ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.94723ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.381259ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.641342ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (213.509849ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.975726ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.459368ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.508572ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.88816ms) +✔ engine: a malformed JSONL line fails the turn, not the process (27.559121ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.586523ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.492888ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.636611ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.512112ms) +✔ gateway: op 9 resumable resumes (0.373826ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.803472ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.614049ms) +✔ gateway: close() is final and unparseable frames are ignored (0.530331ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (81.625198ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (41.964148ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (72.224801ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.286814ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (78.59772ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (93.72916ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (98.25941ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (215.890099ms) +✔ git: push pushes the named branch only and reports up to date (74.044334ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (96.058376ms) +✔ git: the credential helper answers get over https from a private file and nothing else (210.705918ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (745.023601ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.025639ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (69.027952ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.693744ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.298745ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (36.122146ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (311.892988ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.493593ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.693381ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.144619ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (42.36655ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (136.66673ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (92.533816ms) +✔ notices: a kind is recorded per UTC day and found again (0.701431ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.390899ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.695456ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.896744ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.432216ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (36.44687ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (28.625011ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (80.139026ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (699.104521ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.645564ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.203417ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.587189ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.939762ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.165359ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.619588ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.648275ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.679636ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.27621ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (25.214832ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.41301ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (5.041797ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (2.489345ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.406435ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (1.925722ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1009.192108ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.456862ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (5.979714ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.17808ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.203811ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.217767ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (2.976347ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.32391ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (3.997208ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.305791ms) +✔ tools: listing and search caps hold (10.842946ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.87988ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.263919ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.085459ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.933655ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.790534ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.958472ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.769913ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.634356ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.999087ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1029.901099ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.252704ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.209937ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.428319ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.193139ms) +ℹ tests 178 +ℹ suites 0 +ℹ pass 178 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2616.727677 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-ledger.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-ledger.txt new file mode 100644 index 00000000..a41316f2 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-ledger.txt @@ -0,0 +1,86 @@ +✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (134.954983ms) +✔ the raw path accepts nothing else, and refuses before curl runs (396.067229ms) +✔ the file checks still apply on the raw path: mode, symlink, missing, directory (299.307677ms) +✔ the raw path base URL has no override (75.479901ms) +✔ the JSON path is unchanged, and JSON never falls through to the raw path (244.190129ms) +✔ a file that changes between the two reads refuses before curl runs, with or without a body (768.730985ms) +✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (760.643115ms) +✔ real helper GET HTTP 200 preserves exit 0 without credentials (10.890804ms) +✔ real helper POST HTTP 201 preserves exit 0 without credentials (12.357074ms) +✔ real helper GET HTTP 403 preserves exit 1 without credentials (11.329849ms) +✔ fixture git subjects only, follow-ups and three session kinds (124.711972ms) +✔ text and JSON carry same numbers, open and truncated title (179.540406ms) +✔ missing credentials exit 2, no-issues never calls API and shows unknown (155.769931ms) +✔ empty range gives no rows and zero totals (114.086049ms) +✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (111.543985ms) +✔ close-only issue included, even median, missing metadata stays unknown (111.772486ms) +✔ unique commits but per-issue links count multiple tags once each (125.111404ms) +✔ page cap refuses rather than silently undercounting (89.59972ms) +✔ bad API payload not JSON refuses (88.226538ms) +✔ bad API payload {} refuses (90.100566ms) +✔ bad API payload [{"number":1}] refuses (101.235119ms) +✔ partial or malformed session log refuses with location, not content (116.26915ms) +✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (109.365003ms) +✔ no sessions is an empty table; symlink source refuses (168.31823ms) +✔ reads only refactor even when another branch is checked out (103.45968ms) +✔ invalid dates, reverse dates and duplicate options refuse (73.408046ms) +✔ T3 agent assignments do not count as human in Table 2 (134.876844ms) +✔ preamble parsing and issue number boundaries (0.571117ms) +✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.185679ms) +✔ no closed issues with human messages means undefined ratio, not invented zero (0.229822ms) +✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (235.126294ms) +✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (425.172893ms) +✔ T3: a HOME with no database exits 1 and names --no-t3 (92.846957ms) +✔ T3: a file that is not a database exits 1 and names --no-t3 (100.558953ms) +✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (117.947984ms) +✔ T3: an unmapped thread addressed as a seat exits 1 (114.942206ms) +✔ T3: a header to another thread id is not cross-checked (120.04112ms) +✔ T3: no project, or two, for this root exits 1 (319.046233ms) +✔ T3: a removed column exits 1 and names it (110.64328ms) +✔ T3: a missing table exits 1 and names it (105.804401ms) +✔ T3: a counted row with an unknown role exits 1 without its text (122.916714ms) +✔ T3: a counted row with non-text content exits 1 without its text (122.037071ms) +✔ T3: a counted row with an unparseable created_at exits 1 without its text (119.943393ms) +✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (206.298998ms) +✔ T3: a human message with no event counts in humanWithoutEvent (118.525102ms) +✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (225.189178ms) +✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (230.956789ms) +✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (57.576409ms) +✔ T3: a symlink at ~/.t3 exits 1 (92.527603ms) +✔ T3: a symlink at ~/.t3/userdata exits 1 (91.929265ms) +✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (88.505097ms) +✔ T3: with --t3-db, a symlinked file or directory exits 1 (168.561821ms) +✔ T3 WAL: the newest message only in -wal, writer attached, is counted (134.473315ms) +✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (125.050087ms) +✔ T3 WAL: -wal without -shm in a writable directory is read (138.735131ms) +✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (141.22079ms) +✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (114.288937ms) +✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5119.32654ms) +✔ a done row whose closing issue is open is a violation; a row that is not done is not (2.127035ms) +✔ an issue several rows close is expected closed only once all of them are done (0.547392ms) +✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.405754ms) +✔ each owner of an in-progress or in-review row gets one liveness class (8.590925ms) +✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.445679ms) +✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.27584ms) +✔ the text section always ends in a count and a result, and never prints a full pass (0.388622ms) +✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (28.135605ms) +✔ issue states: open list first, then the metric page, then at most 10 lookups (310.217984ms) +✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (396.107987ms) +✔ the open list refuses on a failed call or a bad record, and never echoes the helper (193.796618ms) +✔ a helper call past the deadline is killed with its child, and the call reports it (2010.201657ms) +✔ readQueue loads queue.json through the queue validator and refuses anything else (93.350514ms) +✔ protected changes list every in-range entry that changes a required or parked row (239.944865ms) +✔ the CLI prints the queue section above the weekly table and under a queue key in --json (530.535263ms) +✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (209.41641ms) +✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (278.064187ms) +✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (255.094423ms) +✔ --unsupported-runtime repeats once per seat and takes a seat name (324.625713ms) +✔ an unreadable config makes every owner invalid instead of passing them (128.44478ms) +ℹ tests 78 +ℹ suites 0 +ℹ pass 78 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 11216.78639 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-mosaic.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-mosaic.txt new file mode 100644 index 00000000..290e2652 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-mosaic.txt @@ -0,0 +1,77 @@ +✔ pure resolution selects current default or explicit enrolled account (2.564533ms) +✔ scope is explicit, bounded and never inferred (2.267411ms) +✔ fork pin is preserved against default change, override, missing account and revocation (1.104979ms) +✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (1.036968ms) +✔ only explicit synthetic credential forms and internal fixture stores admitted (9.495556ms) +✔ two concurrent workspaces of the same agent publish distinct complete private generations (79.481947ms) +✔ same execution ID is exclusively claimed and cannot overwrite a published generation (36.445001ms) +✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (56.092833ms) +✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (90.591743ms) +✔ credential lock contention refuses without duplicate side effects (14.186768ms) +✔ symlinked pre-existing final target is refused and never followed (28.81338ms) +✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.38451ms) +✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (28.638912ms) +✔ expired credentials refresh under transaction and subsequent generation reuses rotation (100.322375ms) +✔ refresh failure retains prior generation and store state (72.894091ms) +✔ refresh timeout retains prior generation and store state (146.852708ms) +✔ refresh malformed retains prior generation and store state (76.992508ms) +✔ concurrent refresh on same account refuses contention while unrelated account proceeds (216.279829ms) +✔ invalid refresh options refuse before burning claim (36.084975ms) +✔ fixed fake process rotates both OAuth fields without mutating caller input (46.226044ms) +✔ concurrent isolated processes preserve separate provider credentials (42.49935ms) +✔ fake failure is refused with fixed diagnostics (38.402014ms) +✔ fake malformed is refused with fixed diagnostics (27.556787ms) +✔ fake timeout is refused with fixed diagnostics (104.233639ms) +✔ fake unchanged is refused with fixed diagnostics (29.060453ms) +✔ caller executable/environment injection is rejected before spawning (0.432498ms) +✔ valid fixture tree validates and lists without secrets (95.518001ms) +✔ unknown-field refuses (0.660398ms) +✔ invalid-id refuses uppercase and traversal shapes (0.449262ms) +✔ plain-http baseUrl requires allowInsecureTransport (0.486138ms) +✔ native provider rejects allowInsecureTransport (0.185017ms) +✔ unsupported credential type and kind refuse (0.256722ms) +✔ account provider-path mismatch refuses (0.18805ms) +✔ profile account refs must be provider/account shaped (0.356319ms) +✔ seat selection accepts fork pin field, validates account refs (1.724632ms) +✔ harness manifest id must equal executable (gate 1) (0.254135ms) +✔ CLI validate: duplicate provider id across files refuses (41.184728ms) +✔ CLI validate: missing referenced provider/account refuse (35.552582ms) +✔ CLI validate: broken JSON refuses without secret echo (35.593541ms) +✔ CLI usage errors exit 2 (66.27167ms) +✔ credential.json sibling presence does not break validation and is never read (77.899926ms) +✔ D1 missing, empty and structurally empty roots refuse, no list projection (228.434182ms) +✔ D1 required directory auth cannot be absent (73.57918ms) +✔ D1 required directory auth/providers cannot be absent (67.812571ms) +✔ D1 required directory auth/accounts cannot be absent (70.125413ms) +✔ D1 required directory auth/settings cannot be absent (73.316956ms) +✔ D1 required directory harnesses cannot be absent (69.291484ms) +✔ D1 root file and unreadable metadata refuse (132.907832ms) +✔ D2 no symlink traversal at auth/providers/openai-codex.json (67.123961ms) +✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (71.5302ms) +✔ D2 no symlink traversal at auth/providers (62.132478ms) +✔ D2 no symlink traversal at auth (63.923136ms) +✔ D2 root and ancestor symlinks and lexical traversal refuse (202.630806ms) +✔ private filesystem modes enforced for root (58.24232ms) +✔ private filesystem modes enforced for auth (56.842035ms) +✔ private filesystem modes enforced for auth/providers/openai-codex.json (61.433163ms) +✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (73.081077ms) +✔ D3 numeric version 1 only across all record kinds (1.556293ms) +✔ D4 nested unknown keys and missing per-kind required fields refuse (72.551004ms) +✔ D5 unenrolled default refuses even when account exists (80.506059ms) +✔ D6 provider/account credential type must match (90.499001ms) +✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.661626ms) +✔ D8 URLs reject embedded credentials and unsupported protocols without echo (199.726941ms) +✔ D9 malformed JSON diagnostics contain no content excerpt (80.056592ms) +✔ D10 missing metadata is missing-path, not invalid-json (78.392131ms) +✔ D10 library returns no partial entries on any invalid record (88.439502ms) +✔ null/scalar/array metadata refuses without stack or echo (279.003391ms) +✔ credential sibling is never opened, even when an unreadable symlink (35.059798ms) +✔ oversized metadata refuses before parsing (64.816327ms) +ℹ tests 69 +ℹ suites 0 +ℹ pass 69 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2563.273154 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-queue.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-queue.txt new file mode 100644 index 00000000..37956415 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-queue.txt @@ -0,0 +1,158 @@ +✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1359.396234ms) +✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1043.514465ms) +ℹ git commit -e: index.lock free during the editor +✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (1003.056852ms) +ℹ git commit -e -- src.txt: index.lock held during the editor +✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1059.246579ms) +✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1083.559062ms) +✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1009.372244ms) +✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1315.819025ms) +✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (903.926148ms) +✔ F1: a shared-index change during the procedure is not committed (1024.559005ms) +✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (973.327088ms) +✔ F1: a missing or a different hook refuses (680.672195ms) +✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1105.291901ms) +✔ F1: the canary refuses a hook that git would not run (565.674364ms) +✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (937.491075ms) +✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (876.982083ms) +✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (849.615844ms) +✔ bootstrap: the archived tests and validator run outside any repository (847.378038ms) +✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (902.082207ms) +✔ general: a queue write after the snapshot is not committed (1172.568202ms) +✔ general: a snapshot whose log does not extend the base refuses (891.747672ms) +✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (164.662274ms) +✔ general: environment overrides, a linked worktree and usage (563.304179ms) +✔ general: a queue path staged before the run refuses at step 1 (657.175219ms) +✔ general: HEAD's queue tests failing in the archive refuse (798.099521ms) +✔ genesis document serializes deterministically and replays (7.097969ms) +✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (4.464942ms) +✔ a tampered result, receipt or viewSha fails replay (4.829576ms) +✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.324341ms) +✔ add: defaults for an ordinary seat, privileged extras, refusals (7.255952ms) +✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (8.722311ms) +✔ matrix: release, review round, changes requested and waiting-on-jason (23.167863ms) +✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (16.454239ms) +✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (41.520243ms) +✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (4.058814ms) +✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1577.031375ms) +✔ matrix: blocked keeps the claim and returns only to previousState (3.998727ms) +✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.748741ms) +✔ field edits: who may change what (6.245511ms) +✔ set issues keeps a logged narrowing of closes (N10) (3.086297ms) +✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.049235ms) +✔ every accepted text renders to nine cells on every row (N8) (27.328603ms) +✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.63157ms) +✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.801356ms) +✔ replay holds every op id to the caller's rule (N11) (5.507886ms) +✔ note: owner, listed reviewer or privileged; empty clears (1.259683ms) +✔ assign moves the claim with the owner; done clears it (2.805933ms) +✔ render is byte-stable and escapes pipes (0.624674ms) +✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.63839ms) +✔ next: resume, then review, then start, then wait, then nothing; lowest id first (20.837893ms) +✔ canonical args make a retry's identity independent of list order (0.332538ms) +✔ manifests, headings and blob ids (0.471438ms) +✔ the migration map: one queue-map block, exact keys (0.648515ms) +✔ every call but `queue` reaches the seat CLI exactly as before A2 (756.23544ms) +✔ `queue` reaches the queue CLI with the rest of the arguments (205.692746ms) +✔ the pre-A2 fixture is the script A2 changed (0.314121ms) +✔ acquire publishes the record by link; release removes only its own lock (7.322445ms) +✔ a kill between the temp write and the link leaves no lock (61.316673ms) +✔ a short or failed temp write refuses and leaves no lock and no temp (2.982791ms) +✔ a link error other than EEXIST refuses (1.670068ms) +✔ an error after the link releases the lock: unreadable gate, failing temp stat (4.394146ms) +✔ a release that fails on a gate path is reported, never a stack trace (P1) (6.072062ms) +✔ a paused holder: another writer waits 10 s, then refuses naming it live (10079.567011ms) +✔ two concurrent unlockers: the second refuses on the gate (22.891652ms) +✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (1.872028ms) +✔ a writer publishing during an unlock, gate first: the writer releases and refuses (1.780997ms) +✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (2.048622ms) +✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (3.441235ms) +✔ the same pid and start on a different boot is mismatch (0.809737ms) +✔ a foreign host is unknown whatever the local pid says; unlock refuses (47.840307ms) +✔ unreadable /proc: classification is unknown and acquire refuses (0.633175ms) +✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.176352ms) +✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (3.159476ms) +✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (2.007105ms) +✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (0.861928ms) +✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (4.030107ms) +✔ the migration map validates and renders the golden genesis table (6.955996ms) +✔ the marked QUEUE.md holds every row and parked item between its markers (2.432104ms) +✔ map-check reports each kind of drift (7.468329ms) +✔ a request posts once as the requester; a retry sends nothing (703.888211ms) +✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (3305.854658ms) +✔ the pre-send checks: GET user must name the requester, under the deadline (1052.458733ms) +✔ the lead's request refuses a token for login sage (560.0714ms) +✔ the credential file: the seat's own, 0600, no symlink, never the shared default (475.694335ms) +✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1524.494778ms) +✔ a same-op retry after a kill sends nothing, even with a stale view (2272.260285ms) +✔ a held lock at the outcome exits 3 and names what the transport said (555.446678ms) +✔ late outcomes: after an abandon, and after a resolve with the same or another id (1465.847919ms) +✔ resolve checks the comment: issue, markers, round, candidate and author (1460.110723ms) +✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (538.605623ms) +✔ validateRow checks a request round's shape, which every replayed entry must keep (391.86327ms) +✔ request, changes, a new candidate, approval: every round pinned; no review files (1351.227244ms) +✔ a row with no reviewers opens a round that posts nothing (813.68075ms) +✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1236.823625ms) +✔ semantics: v1 entries replay as before; review entries need v2 (323.027824ms) +✔ set reviewers refuses the row's owner (2026-09-28) (250.683638ms) +✔ the owner records no verdict, even as a listed reviewer (333.841282ms) +✔ a request comment over the length limit is not sent (366.233983ms) +✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (393.740227ms) +✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1813.519805ms) +✔ genesis: refusals before anything is written (543.210455ms) +✔ genesis: the map must be committed, well formed, with committed briefs and seats (637.533147ms) +✔ genesis: markers, a stray witness, once only; a retry returns the receipt (542.412289ms) +✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (321.269513ms) +✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (658.068971ms) +✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (546.77919ms) +✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (626.444551ms) +✔ a retried add returns the id it first allocated, after reassignment and after done (657.347337ms) +✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (456.904055ms) +✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (883.92666ms) +✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (602.709858ms) +✔ add, set reviewers and assign refuse the row's owner as a reviewer (387.905069ms) +✔ the working-brief check: a changed working copy refuses the start and flags next (558.509797ms) +✔ next: resume first, then nothing for an idle seat; needs a seat (292.038442ms) +✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (576.718678ms) +✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1051.037347ms) +✔ a hand edit to queue.json refuses every verb, reads included (526.78895ms) +✔ verify and render --check leave bytes and mtimes unchanged (381.683845ms) +✔ render is byte-stable across runs and repositories (213.881165ms) +✔ snapshot and verify --snapshot (687.817463ms) +✔ usage errors exit 4 (515.059669ms) +✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (248.743753ms) +✔ a rename failure: nothing visible, temp removed (166.197221ms) +✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (180.139347ms) +✔ a directory fsync failure, then sync names the op (311.806093ms) +✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (159.825793ms) +✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (135.519792ms) +✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (152.115748ms) +✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (141.713214ms) +✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (151.4709ms) +✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (147.218926ms) +✔ unlock prints a swapped gate's warning on stderr, the result on stdout (117.31543ms) +✔ a view write that fails keeps the op and reports a stale view (124.551407ms) +✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (590.303338ms) +✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (636.373628ms) +✔ SIGKILL after the witness, before the view: the stale refusal names the op (579.836375ms) +✔ SIGKILL after the view, before the receipt: the retry returns the receipt (586.594766ms) +✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (143.893022ms) +✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (839.488414ms) +✔ a deleted witness: refused after the locked recheck; accept-history records it absent (208.416622ms) +✔ a header edit during a write: the op stands, the view write is skipped with a warning (133.269398ms) +✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (130.929076ms) +✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (129.758744ms) +✔ a writer paused before and after the witness rename: readers see a tail, then a match (136.820647ms) +✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (481.631398ms) +✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (453.807825ms) +✔ the platform check refuses other filesystems (120.472204ms) +✔ tmpfs passes only a test layer that allows it (N5) (146.502172ms) +✔ unlock keeps a multi-line lock record on stdout (P3) (178.016141ms) +ℹ tests 148 +ℹ suites 0 +ℹ pass 148 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 21868.207103 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-seat.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-seat.txt new file mode 100644 index 00000000..d0ea8a84 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-seat.txt @@ -0,0 +1,27 @@ +✔ resolveSeat: by name under --repo resolves the repo layout (1.350957ms) +✔ resolveSeat: by path resolves the fleet layout (0.347792ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.872088ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.724737ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.974303ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.503027ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.392817ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (35.048211ms) +✔ CLI launch: --harness lands in the record (30.364047ms) +✔ CLI launch: the launch script's own exit code passes through (28.321911ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (60.58371ms) +✔ CLI launch: omitting --task records an empty string, not null (27.53592ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (89.974265ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (133.502783ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.383452ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.541156ms) +✔ 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.716209ms) +✔ 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 (7.510892ms) +✔ 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 (268.408271ms) +ℹ tests 19 +ℹ suites 0 +ℹ pass 19 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 756.917863 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-tasks.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-tasks.txt new file mode 100644 index 00000000..5682df29 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-tasks.txt @@ -0,0 +1,59 @@ +✔ the boot config is checked before anything starts (24.946103ms) +✔ a business with no tracker entry refuses task verbs (9.850969ms) +✔ credential.expiring and .expired are recorded once per instance (27.095521ms) +✔ a token file that changes on disk records credential.changed (11.11232ms) +✔ autostart polls, reconciles and retries a startup the tracker was down for (34.11349ms) +✔ a refusal a restart must clear is not retried by the poll (9.081344ms) +✔ a poll that fires while two are queued is dropped (12.54199ms) +✔ close waits for a running verb and refuses one that has not started (39.946263ms) +✔ the bundled Vikunja is the pinned upstream image the runbook names (0.731419ms) +✔ every published port is on 127.0.0.1, and no secret is in the file (0.288749ms) +✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (101.994246ms) +✔ the recorded task bodies pass the checks S3 applies to every read (0.494715ms) +✔ the client works against the fake over real HTTP with the platform fetch (95.087131ms) +✔ a correct install starts, and the first reconcile records tasks that already exist (38.500326ms) +✔ verbs refuse while a business is starting and after startup refused it (13.225827ms) +✔ startup refuses a token that can do more than its role needs (33.922614ms) +✔ startup refuses an unsupported version and flags an untested one (24.585917ms) +✔ startup refuses a board that the runbook did not install (33.484823ms) +✔ startup refuses a project the sync bot cannot read (6.477271ms) +✔ startup refuses a configured label the pm bot cannot see (7.180722ms) +✔ startup refuses an expired credential and a missing sync credential (16.764719ms) +✔ an unreachable tracker refuses with tracker-unavailable (8.502115ms) +✔ an edit in the UI is recorded once, with the fields that changed (82.38956ms) +✔ a move between open buckets is seen on the board, though updated does not change (39.376206ms) +✔ a person's comment is counted and a bot's is not (67.280116ms) +✔ the hourly reconcile catches a comment through comment_count (77.489973ms) +✔ a task closed in the UI leaves the open view with its done bucket (111.90947ms) +✔ a task that leaves the board is recorded as deleted, moved or out of reach (64.889475ms) +✔ a poll that read before a verb wrote does not overwrite the verb (34.482803ms) +✔ a tracker fault during a tick is reported and the next tick catches up (33.274217ms) +✔ a malformed answer refuses the tick with tracker-shape (10.561368ms) +✔ no token value reaches the database, the log or a refusal (44.460625ms) +✔ the first look at a task counts only comments inside the window (34.340508ms) +✔ task.create needs a recorded human request and a requirement id (41.174823ms) +✔ only labels named in the business file can be written (24.082432ms) +✔ task.schedule sets and clears a due date and relations (30.025414ms) +✔ assign and reassign move the role bots and record task.assigned (53.210817ms) +✔ task.update.assigned is for the assignee and records task.state (51.25698ms) +✔ a wrong expected digest records task.conflict and writes nothing (17.025396ms) +✔ a cross-role verb needs a resolved decision, used once (21.197038ms) +✔ task.close needs a verdict; after it every verb refuses with task-done (20.975094ms) +✔ a lost answer is settled by a re-read and never retried (23.581416ms) +✔ a create whose answer is lost is reported uncertain, and the poll finds the task (34.523382ms) +✔ a task the sync bot cannot read refuses and records nothing (14.598914ms) +✔ verbs and polls for one business run one at a time (88.211598ms) +✔ a due date with milliseconds is written to the second (63.76776ms) +✔ every write landed and the final read failed: the verb succeeds and records what it wrote (35.100869ms) +✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (13.873869ms) +✔ a create whose final read fails succeeds and records task.created (33.617562ms) +✔ an edit between the last write and the final read shows as external on the next poll (35.64984ms) +✔ task.created is recorded when a later label write fails (10.729354ms) +ℹ tests 51 +ℹ suites 0 +ℹ pass 51 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 701.940695 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-node-webui.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-node-webui.txt new file mode 100644 index 00000000..09656885 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-node-webui.txt @@ -0,0 +1,23 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2399.780469ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2619.356647ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2244.443178ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1334.836758ms) +✔ conversation view: a newer session with no readable history keeps the marker (875.725012ms) +✔ conversation view: seats without history say so and offer no reply (534.136133ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (1993.850576ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (52948.739217ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2092.69041ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21864.058473ms) +✔ loopback host and board origin fail closed (6.536127ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (76.90787ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (54.749146ms) +✔ unreachable board reports URL; CLI rejects unsupported options (385.859577ms) +ℹ tests 14 +ℹ suites 0 +ℹ pass 14 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 53238.292445 diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-suite-auth.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-auth.txt new file mode 100644 index 00000000..ac228760 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert/work/s4-follow-up-review/r2-cand-suite-conductor.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-conductor.txt new file mode 100644 index 00000000..f8124d92 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-conductor.txt @@ -0,0 +1,55 @@ +Note: switching to 'd539d8d2cc930ff8707f8d778a78b1fe661a3d99'. + +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 'd539d8d2cc930ff8707f8d778a78b1fe661a3d99'. + +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/filbert/work/s4-follow-up-review/r2-cand-suite-config.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-config.txt new file mode 100644 index 00000000..76c05ef7 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert/work/s4-follow-up-review/r2-cand-suite-discord.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-discord.txt new file mode 100644 index 00000000..b013f9ff --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert/work/s4-follow-up-review/r2-cand-suite-extension-package.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-extension-package.txt new file mode 100644 index 00000000..ed5d72ce --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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: 1029170 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/filbert/work/s4-follow-up-review/r2-cand-suite-foundation.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-foundation.txt new file mode 100644 index 00000000..6338e1e6 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert/work/s4-follow-up-review/r2-cand-suite-queue.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-queue.txt new file mode 100644 index 00000000..1b7b2f4f --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert-scratch/r45b/cand) is not the queue's canonical root (/mnt/storage/src/mosaic-stack) + +queue suite: 27 passed, 0 failed diff --git a/agents/filbert/work/s4-follow-up-review/r2-cand-suite-release.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-release.txt new file mode 100644 index 00000000..1d6ed050 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert/work/s4-follow-up-review/r2-cand-suite-task.txt b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-task.txt new file mode 100644 index 00000000..85aa46f0 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-cand-suite-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/filbert/work/s4-follow-up-review/r2-gate-exits.txt b/agents/filbert/work/s4-follow-up-review/r2-gate-exits.txt new file mode 100644 index 00000000..d132fad5 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-gate-exits.txt @@ -0,0 +1,21 @@ +r2-cand node-business exit 0 +r2-cand node-bus exit 0 +r2-cand node-cli exit 0 +r2-cand node-control-board exit 0 +r2-cand node-conversation exit 0 +r2-cand node-discord exit 0 +r2-cand node-ledger exit 0 +r2-cand node-mosaic exit 0 +r2-cand node-queue exit 0 +r2-cand node-seat exit 0 +r2-cand node-tasks exit 0 +r2-cand node-webui exit 0 +r2-cand suite-auth exit 0 load 3.14 +r2-cand suite-conductor exit 0 load 3.05 +r2-cand suite-config exit 0 load 3.05 +r2-cand suite-discord exit 0 load 2.90 +r2-cand suite-extension-package exit 0 load 2.90 +r2-cand suite-foundation exit 0 load 3.03 +r2-cand suite-queue exit 0 load 3.47 +r2-cand suite-release exit 0 load 3.47 +r2-cand suite-task exit 1 load 3.27 diff --git a/agents/filbert/work/s4-follow-up-review/r2-gate-start.txt b/agents/filbert/work/s4-follow-up-review/r2-gate-start.txt new file mode 100644 index 00000000..148855be --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-gate-start.txt @@ -0,0 +1,4 @@ +2026-10-09T14:13:50Z +3.08 2.83 2.56 +2026-10-09T14:17:19Z +3.27 3.27 2.82 diff --git a/agents/filbert/work/s4-follow-up-review/r2-host-rep1.txt b/agents/filbert/work/s4-follow-up-review/r2-host-rep1.txt new file mode 100644 index 00000000..ca5e4718 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-host-rep1.txt @@ -0,0 +1,23 @@ +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (902.858344ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (220.585964ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (147.221617ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (178.594875ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (197.811862ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (104.677986ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (164.855973ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (101.265774ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (201.87513ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (31.144552ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.658347ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (250.6935ms) +✔ bus start refuses with exit 3 without a notifier config (112.390225ms) +✔ bus start runs until bus stop; status reports it while it runs (707.018127ms) +✔ bus-service.sh renders the unit and installs it into a given directory (32.972042ms) +ℹ tests 15 +ℹ suites 0 +ℹ pass 15 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3660.289017 diff --git a/agents/filbert/work/s4-follow-up-review/r2-host-rep2.txt b/agents/filbert/work/s4-follow-up-review/r2-host-rep2.txt new file mode 100644 index 00000000..035f853c --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-host-rep2.txt @@ -0,0 +1,23 @@ +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (838.773235ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (157.23386ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (107.405806ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (150.039856ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (145.326695ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (77.150352ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (132.897278ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (86.071013ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (160.69536ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (26.872974ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.537904ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (238.835264ms) +✔ bus start refuses with exit 3 without a notifier config (105.950535ms) +✔ bus start runs until bus stop; status reports it while it runs (626.017398ms) +✔ bus-service.sh renders the unit and installs it into a given directory (32.343458ms) +ℹ tests 15 +ℹ suites 0 +ℹ pass 15 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3178.25 diff --git a/agents/filbert/work/s4-follow-up-review/r2-host-rep3.txt b/agents/filbert/work/s4-follow-up-review/r2-host-rep3.txt new file mode 100644 index 00000000..18f2e47f --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-host-rep3.txt @@ -0,0 +1,23 @@ +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (832.29903ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (155.677223ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (80.963878ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (140.109068ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (142.235277ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (86.627976ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (132.329893ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (80.614308ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (144.138594ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.953939ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.10225ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (217.357579ms) +✔ bus start refuses with exit 3 without a notifier config (89.848382ms) +✔ bus start runs until bus stop; status reports it while it runs (613.658454ms) +✔ bus-service.sh renders the unit and installs it into a given directory (31.388514ms) +ℹ tests 15 +ℹ suites 0 +ℹ pass 15 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3054.722278 diff --git a/agents/filbert/work/s4-follow-up-review/r2-host-rep4.txt b/agents/filbert/work/s4-follow-up-review/r2-host-rep4.txt new file mode 100644 index 00000000..30f5a7f9 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-host-rep4.txt @@ -0,0 +1,23 @@ +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (825.461724ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (170.540963ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (94.59329ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (159.735879ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (138.758316ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (85.948291ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (146.550926ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (89.325031ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (140.454576ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.675681ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.233445ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (237.936025ms) +✔ bus start refuses with exit 3 without a notifier config (101.898352ms) +✔ bus start runs until bus stop; status reports it while it runs (624.727884ms) +✔ bus-service.sh renders the unit and installs it into a given directory (31.079198ms) +ℹ tests 15 +ℹ suites 0 +ℹ pass 15 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3149.938828 diff --git a/agents/filbert/work/s4-follow-up-review/r2-host-rep5.txt b/agents/filbert/work/s4-follow-up-review/r2-host-rep5.txt new file mode 100644 index 00000000..c4daa356 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-host-rep5.txt @@ -0,0 +1,23 @@ +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (826.688136ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (155.701496ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (85.464445ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (145.679558ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (135.563112ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (81.127162ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (133.894492ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (80.809498ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (153.987016ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.952831ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.820315ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (226.204527ms) +✔ bus start refuses with exit 3 without a notifier config (97.940577ms) +✔ bus start runs until bus stop; status reports it while it runs (642.659088ms) +✔ bus-service.sh renders the unit and installs it into a given directory (31.9554ms) +ℹ tests 15 +ℹ suites 0 +ℹ pass 15 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3110.071413 diff --git a/agents/filbert/work/s4-follow-up-review/r2-host-reps.txt b/agents/filbert/work/s4-follow-up-review/r2-host-reps.txt new file mode 100644 index 00000000..8dd977a9 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-host-reps.txt @@ -0,0 +1,5 @@ +run 1 exit 0 ℹ pass 15 ℹ fail 0 ℹ duration_ms 3660.289017 +run 2 exit 0 ℹ pass 15 ℹ fail 0 ℹ duration_ms 3178.25 +run 3 exit 0 ℹ pass 15 ℹ fail 0 ℹ duration_ms 3054.722278 +run 4 exit 0 ℹ pass 15 ℹ fail 0 ℹ duration_ms 3149.938828 +run 5 exit 0 ℹ pass 15 ℹ fail 0 ℹ duration_ms 3110.071413 diff --git a/agents/filbert/work/s4-follow-up-review/r2-manifest-mut-after.txt b/agents/filbert/work/s4-follow-up-review/r2-manifest-mut-after.txt new file mode 100644 index 00000000..25064faa --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-manifest-mut-after.txt @@ -0,0 +1,8 @@ +packages/cli/README.md: OK +packages/cli/src/host.mjs: OK +packages/cli/src/notifier.mjs: OK +packages/cli/tests/host.test.mjs: OK +packages/cli/tests/notifier.test.mjs: OK +packages/cli/tests/trackers-boot.test.mjs: OK +packages/discord/tests/journal.test.mjs: OK +scripts/bus-service.sh: OK diff --git a/agents/filbert/work/s4-follow-up-review/r2-mut-summary.txt b/agents/filbert/work/s4-follow-up-review/r2-mut-summary.txt new file mode 100644 index 00000000..783d05f8 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-mut-summary.txt @@ -0,0 +1,49 @@ +N2 killed (fail 1, cancelled 0) 4 s +N4 killed (fail 1, cancelled 0) 3 s +N5 killed (fail 1, cancelled 0) 3 s +M28 killed (fail 1, cancelled 0) 3 s +G150 killed (fail 1, cancelled 0) 4 s +G144 killed (fail 1, cancelled 0) 3 s +F2a killed (fail 2, cancelled 0) 3 s +F2b killed (fail 1, cancelled 0) 4 s +F2c killed (fail 2, cancelled 0) 3 s +F2d killed (fail 1, cancelled 0) 3 s +F2e killed (fail 1, cancelled 0) 3 s +F2f killed (fail 2, cancelled 0) 4 s +J4a killed (fail 1, cancelled 0) 3 s +J4b killed (fail 1, cancelled 0) 3 s +J4c killed (fail 1, cancelled 0) 3 s +J4d killed (fail 1, cancelled 0) 4 s +E1 killed (fail 1, cancelled 0) 3 s +E2 killed (fail 1, cancelled 0) 3 s +G144cb killed (fail 1, cancelled 0) 22 s +G150cb killed (fail 1, cancelled 0) 22 s +G184 killed (fail 1, cancelled 0) 22 s +G188 killed (fail 1, cancelled 0) 23 s +R2a killed (fail 2, cancelled 0) 3 s +R2b killed (fail 1, cancelled 0) 3 s +R2c killed (fail 1, cancelled 0) 4 s +X9 killed (fail 1, cancelled 0) 3 s +X14 killed (fail 1, cancelled 0) 3 s +D3 killed (fail 1, cancelled 0) 4 s +N1a killed (fail 1, cancelled 0) 3 s +N1b killed (fail 1, cancelled 0) 3 s +N1c killed (fail 1, cancelled 0) 3 s +X1 killed (fail 1, cancelled 0) 4 s +X2 SURVIVED 3 s +X3 killed (fail 3, cancelled 0) 3 s +X4 killed (fail 1, cancelled 0) 4 s +X5 killed (fail 1, cancelled 0) 3 s +X6 killed (fail 2, cancelled 0) 4 s +X7 killed (fail 1, cancelled 0) 3 s +X8 killed (fail 1, cancelled 0) 4 s +X10 killed (fail 1, cancelled 0) 4 s +X11 killed (fail 1, cancelled 0) 3 s +X12 killed (fail 3, cancelled 0) 4 s +X13 killed (fail 3, cancelled 0) 3 s +Y1 killed (fail 1, cancelled 0) 3 s +Y2 killed (fail 1, cancelled 0) 4 s +Y3 killed (fail 1, cancelled 0) 3 s +Y4 killed (fail 3, cancelled 0) 3 s +Y5 SURVIVED 4 s +Y6 killed (fail 1, cancelled 0) 3 s diff --git a/agents/filbert/work/s4-follow-up-review/r2-mut-time.txt b/agents/filbert/work/s4-follow-up-review/r2-mut-time.txt new file mode 100644 index 00000000..abd95609 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-mut-time.txt @@ -0,0 +1,4 @@ +2026-10-09T14:17:54Z +2.18 3.01 2.75 +2026-10-09T14:21:53Z +5.97 4.30 3.32 diff --git a/agents/filbert/work/s4-follow-up-review/r2-node24.txt b/agents/filbert/work/s4-follow-up-review/r2-node24.txt new file mode 100644 index 00000000..7781c99a --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-node24.txt @@ -0,0 +1,255 @@ +v24.21.0 +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (141.769804ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (152.273622ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (103.386638ms) +✔ decide prints a declining choice as declining (105.717246ms) +✔ an unknown outcome is reported once and never resent (95.620592ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (91.07245ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (102.175708ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (90.422347ms) +✔ every human command refuses inside an agent run before it touches the bus (62.937334ms) +✔ usage errors exit 4; no business and no host is a usage error (78.399204ms) +✔ agents and tasks print through the broker (124.946145ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.404614ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (120.712546ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.912357ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (60.628302ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (57.595376ms) +✔ a business without tracker.baseUrl gets no trackers entry (51.110821ms) +✔ an unknown business and a broken system config refuse with exit 3 (97.782728ms) +✔ empty views say so (1.527693ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.777451ms) +✔ tasks print the tracker fields the snapshot carries (0.340434ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1130.554917ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (238.387028ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (147.478303ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (181.442764ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (209.697387ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (132.579311ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (226.551717ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (148.41294ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (238.380997ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (38.305438ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.915933ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (304.458307ms) +✔ bus start refuses with exit 3 without a notifier config (118.548092ms) +✔ bus start runs until bus stop; status reports it while it runs (732.926663ms) +✔ bus-service.sh renders the unit and installs it into a given directory (40.841348ms) +✔ zoned uses the IANA zone across DST (107.844883ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (149.85251ms) +✔ two blocking decisions get two DMs with different nonces (133.956209ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.379575ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (117.603558ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (110.104093ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (98.619658ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (103.579862ms) +✔ 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 (209.973411ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (188.805907ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (100.275339ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (58.112958ms) +✔ an inbox read failure is logged and the next poll retries (1.140292ms) +✔ no Discord id reaches the journal or the log (67.028045ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (15.850793ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (43.771903ms) +✔ the journal: a whole file that is one torn line truncates to empty (27.663961ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.647996ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.679101ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (13.058057ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.529221ms) +✔ 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.593785ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.452364ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.530892ms) +✔ digest content stays within Discord's 2000 characters (0.287362ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.78763ms) +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 (582.967903ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (77.125422ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2286.386913ms) +✔ busExit and refuseInsideAgent (0.68182ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (4.124214ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.888491ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.787274ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.563093ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (24.260762ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (13.803882ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (17.633728ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (7.168627ms) +✔ authorize: open channel, listed user (1.864608ms) +✔ authorize: wrong guild (0.184671ms) +✔ authorize: no guild (DM) (0.175514ms) +✔ authorize: unlisted channel (0.175749ms) +✔ authorize: unknown channel, no info (0.258783ms) +✔ authorize: thread of listed parent (0.191099ms) +✔ authorize: thread of unlisted parent (0.228561ms) +✔ authorize: text channel that is not a thread and not listed (0.163583ms) +✔ authorize: unlisted user (0.207416ms) +✔ authorize: no author (0.834329ms) +✔ authorize: bot author (listed id, bot flag) (0.164321ms) +✔ authorize: system author (0.142859ms) +✔ authorize: the bot itself (0.104691ms) +✔ authorize: webhook (0.130517ms) +✔ authorize: mention channel without mention (0.162923ms) +✔ authorize: mention channel with bot mention (0.16085ms) +✔ authorize: mention channel with @everyone only (0.145788ms) +✔ authorize: mention channel mentioning someone else (0.095506ms) +✔ authorize: mention channel, content says @bot but mentions empty (1.685693ms) +✔ authorize: private thread under mention channel, mentioned (0.135411ms) +✔ authorize: private thread under mention channel, not mentioned (0.086634ms) +✔ authorize: thread in another guild per channel info (0.086639ms) +✔ authorize: not an object (0.0648ms) +✔ authorize: no id (0.053624ms) +✔ authorize: oversize content is accepted and flagged (0.070343ms) +✔ authorize: exactly the limit is not oversize (0.067877ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.615508ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.216684ms) +✔ binding: a complete binding validates and is frozen (2.920313ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.050777ms) +✔ binding: empty allowlists refuse (0.443829ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.538863ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.721571ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (2.580814ms) +✔ binding: file must be 0600, regular, not a symlink (3.009543ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (4.741959ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (132.806173ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.070864ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (486.306582ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (225.368928ms) +✔ cli: run refuses when STOP is present, before any network use (174.09589ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (3.446217ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.469541ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (29.077287ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (47.183354ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.564463ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.675319ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (2.475748ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (4.299535ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.433693ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (5.374929ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.537875ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (5.532757ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (46.167761ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (39.99824ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.721103ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.232926ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.826314ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (8.840133ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.63549ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.346107ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (2.446392ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.117435ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.622448ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.388565ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.290782ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (4.306678ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.927198ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (3.088392ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.374487ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.771862ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.494126ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.789521ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.753466ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.599616ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (77.929401ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (59.683403ms) +✔ engine: one prompt, one turn, text and usage come back (56.446291ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (350.233449ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (239.075436ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (115.200516ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (239.472932ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (136.268128ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.921412ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.223879ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (2.953429ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (1.156744ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (442.049853ms) +✔ engine: a malformed JSONL line fails the turn, not the process (36.289011ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (63.493515ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (10.083839ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (3.432487ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.798002ms) +✔ gateway: op 9 resumable resumes (0.483228ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.262745ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.683489ms) +✔ gateway: close() is final and unparseable frames are ignored (0.608011ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (164.564844ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (66.143892ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (141.676298ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (1.060595ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (128.022133ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (122.184762ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (101.696003ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (217.96085ms) +✔ git: push pushes the named branch only and reports up to date (97.574455ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (109.873902ms) +✔ git: the credential helper answers get over https from a private file and nothing else (231.399664ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (354.724763ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (8.295395ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (91.1058ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.298946ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.761997ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (58.387451ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (411.67986ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.536515ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.226468ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.162998ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (37.348808ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (157.961807ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (104.45588ms) +✔ notices: a kind is recorded per UTC day and found again (1.936241ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.438537ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.617516ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.687688ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (60.882453ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (47.462ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (48.010743ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (108.172473ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (794.168627ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (4.238085ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.474705ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.68235ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.838098ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.24231ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (7.632286ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.690381ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.206832ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.797699ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.951135ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (24.710361ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.676381ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (10.197294ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.116895ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.386708ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1019.364528ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.780319ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (8.126735ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (2.973114ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.375358ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (4.307384ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.600184ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.496428ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.34992ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.247788ms) +✔ tools: listing and search caps hold (21.175682ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.052507ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (13.661019ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.620156ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.696349ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (8.274881ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.652499ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.0437ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (5.032996ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.901505ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1035.568346ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (7.018237ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.456768ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (4.00297ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (4.707789ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 244 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 4258.836971 diff --git a/agents/filbert/work/s4-follow-up-review/r2-probe.txt b/agents/filbert/work/s4-follow-up-review/r2-probe.txt new file mode 100644 index 00000000..21de8c5d --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2-probe.txt @@ -0,0 +1,33 @@ +PROBE T6 sends at min 0 30 60 90 120 gave-up at min 120 +PROBE T7 outcomes refused,refused,refused,refused,confirmed sends at min 0 30 60 90 120 +PROBE T8 first send at min 90 log lines 0 +PROBE T9 sends at min 0 30 60 90 120 gave-up at min 120 +✔ R2-T6 crash loop against a permanent 403: restart every 15 s (202.62209ms) +✔ R2-T7 binding fixed at 100 min (110.981781ms) +✔ R2-T8 a refusal line an hour in the future holds the DM (46.05921ms) +✔ R2-T9 a permanent 401 (69.329245ms) +PROBE T1 sends at +0s +1800s +3600s +5400s +7200s +PROBE T1 gave-up at +7200s = 120.0 min +PROBE T2 after 4 h: outcomes refused,refused,confirmed +PROBE T2 sends 3 +PROBE T3 sends before restart 1 after one tick post-restart 1 (same clock second) +PROBE T4 dm sends 17 gave-up false +PROBE T5 digest sends in 1 h 7 gave-up false +PROBE A1 tick {"dms":1,"digest":false,"failed":0} line {"at":"2026-10-08T05:00:00.000Z","kind":"dm","decision":"3a28a6d6-e2ca-42a9-8397-f7e316020abb","outcome":"confirmed","messageId":123} +PROBE A1 reopen 3 notify journal line 1 is malformed (messageId): /notify/demo/sent.jsonl +PROBE A2 ok sent d1 days 2026-10-08 refusals [["d2",1]] +✔ F2-T1 time from first refusal to gave-up, polling every POLL_MS (84.865022ms) +✔ F2-T2 binding fixed after 60 min: the DM never lands (109.284341ms) +✔ F2-T3 restart drops the backoff: next attempt is immediate (9.578122ms) +✔ F2-T4 500s for 6 h: no gave-up (155.600387ms) +✔ F2-T5 digest refusals: retried, never gave-up (27.298681ms) +✔ J4-A1 append does not type-check: a numeric messageId bricks the next open (11.168654ms) +✔ J4-A2 a line the row 39 writer produced still opens (7.156027ms) +ℹ tests 11 +ℹ suites 0 +ℹ pass 11 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 486.030224 diff --git a/agents/filbert/work/s4-follow-up-review/r2/mut-G144cb.txt b/agents/filbert/work/s4-follow-up-review/r2/mut-G144cb.txt new file mode 100644 index 00000000..410a9604 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2/mut-G144cb.txt @@ -0,0 +1,284 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (33.430185ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (38.647609ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (16.628942ms) +✔ decide prints a declining choice as declining (16.102742ms) +✔ an unknown outcome is reported once and never resent (15.806524ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.947242ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (16.71449ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (15.550033ms) +✔ every human command refuses inside an agent run before it touches the bus (18.407907ms) +✔ usage errors exit 4; no business and no host is a usage error (17.040133ms) +✔ agents and tasks print through the broker (16.530945ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.479806ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (80.587441ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (61.472451ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (58.488409ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (54.472491ms) +✔ a business without tracker.baseUrl gets no trackers entry (60.289494ms) +✔ an unknown business and a broken system config refuse with exit 3 (79.174067ms) +✔ empty views say so (1.225616ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.223658ms) +✔ tasks print the tracker fields the snapshot carries (0.224307ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (967.792159ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (153.484669ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (72.192414ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (126.114769ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (120.266521ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (75.987301ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (122.32405ms) +✖ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (71.855501ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.968407ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.737252ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.006516ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (197.078112ms) +✔ bus start refuses with exit 3 without a notifier config (84.324638ms) +✔ bus start runs until bus stop; status reports it while it runs (594.597256ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.502055ms) +✔ zoned uses the IANA zone across DST (26.591316ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (34.463206ms) +✔ two blocking decisions get two DMs with different nonces (27.459827ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.440554ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.922525ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (32.165195ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.46734ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.580556ms) +✔ 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 (150.989015ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (50.415953ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.153184ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.686392ms) +✔ an inbox read failure is logged and the next poll retries (0.747206ms) +✔ no Discord id reaches the journal or the log (9.673086ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.280055ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.629776ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.41181ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.501657ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.620914ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.07509ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.370057ms) +✔ 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.535436ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.433342ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.517763ms) +✔ digest content stays within Discord's 2000 characters (0.321536ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.731827ms) +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 (358.714597ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (59.979852ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2263.998519ms) +✔ busExit and refuseInsideAgent (0.424488ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (4.301903ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.245565ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (1.335049ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.532049ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.850386ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (6.635369ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.135104ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (4.456154ms) +✔ authorize: open channel, listed user (2.796405ms) +✔ authorize: wrong guild (0.243694ms) +✔ authorize: no guild (DM) (0.196695ms) +✔ authorize: unlisted channel (0.191442ms) +✔ authorize: unknown channel, no info (0.21877ms) +✔ authorize: thread of listed parent (0.220476ms) +✔ authorize: thread of unlisted parent (0.185016ms) +✔ authorize: text channel that is not a thread and not listed (0.263556ms) +✔ authorize: unlisted user (0.196028ms) +✔ authorize: no author (0.337387ms) +✔ authorize: bot author (listed id, bot flag) (0.189505ms) +✔ authorize: system author (0.133521ms) +✔ authorize: the bot itself (0.118869ms) +✔ authorize: webhook (0.128956ms) +✔ authorize: mention channel without mention (0.154873ms) +✔ authorize: mention channel with bot mention (0.204719ms) +✔ authorize: mention channel with @everyone only (0.133345ms) +✔ authorize: mention channel mentioning someone else (0.118821ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.119976ms) +✔ authorize: private thread under mention channel, mentioned (0.130634ms) +✔ authorize: private thread under mention channel, not mentioned (0.094859ms) +✔ authorize: thread in another guild per channel info (0.099368ms) +✔ authorize: not an object (0.127238ms) +✔ authorize: no id (0.082688ms) +✔ authorize: oversize content is accepted and flagged (0.101231ms) +✔ authorize: exactly the limit is not oversize (0.089555ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (2.080337ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.281283ms) +✔ binding: a complete binding validates and is frozen (2.79738ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.534382ms) +✔ binding: empty allowlists refuse (0.407511ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.291826ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.822256ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.265106ms) +✔ binding: file must be 0600, regular, not a symlink (1.579416ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.288639ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (110.122291ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.01053ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (379.361716ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (194.36151ms) +✔ cli: run refuses when STOP is present, before any network use (126.609602ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.783356ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.381267ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (20.067517ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.112707ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.683025ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.307083ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.479437ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.805929ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.087809ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.852412ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.109906ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.166373ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.641064ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.311531ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.872612ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (6.653869ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.243353ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.319219ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.071127ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.735284ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (3.7783ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.474308ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.649118ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.557765ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.444719ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.766803ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.900893ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.977193ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.392079ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.617241ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.381653ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.701541ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.616869ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.447456ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (55.384462ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (56.934495ms) +✔ engine: one prompt, one turn, text and usage come back (49.26446ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (360.704856ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (241.072333ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (114.034292ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.296865ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.669171ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (211.107181ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.46833ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.347741ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.499776ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.824665ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.854857ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.19398ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.602105ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.783537ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.661179ms) +✔ gateway: op 9 resumable resumes (0.56307ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.848531ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.615872ms) +✔ gateway: close() is final and unparseable frames are ignored (0.669467ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.632054ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (58.770665ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (137.00705ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.515415ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (90.154704ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.495559ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (88.299936ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (201.399918ms) +✔ git: push pushes the named branch only and reports up to date (68.973063ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (86.901925ms) +✔ git: the credential helper answers get over https from a private file and nothing else (205.337256ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (740.213274ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (6.220043ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (85.468692ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.992589ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.695564ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (66.982087ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (332.574755ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.45848ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.260568ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.55829ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (47.655548ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (134.348659ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (85.941756ms) +✔ notices: a kind is recorded per UTC day and found again (0.443736ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.915426ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.674921ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.825079ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (49.267442ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (49.397399ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (49.271813ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (87.343516ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (659.456861ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.665567ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.396348ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.633232ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.66151ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.04692ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.447103ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.226838ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.916966ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.694711ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.362178ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.558284ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.57891ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.275022ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.774344ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.304108ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.316705ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.493422ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (6.041306ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.275777ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.200484ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.25519ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.409364ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.757099ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.794515ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.867357ms) +✔ tools: listing and search caps hold (10.10739ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.710026ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (4.864765ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.388275ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.429889ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.409106ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.452582ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.412244ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.7847ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.120391ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1021.450935ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (3.982397ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.222808ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.565299ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.141047ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 21927.25641 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:282:1 +✖ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (71.855501ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: write EPIPE + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:298:3) + 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: Error: write EPIPE + at epipe (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:113:24) + at file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:288:35 + at ChildProcess.send (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:94:23) + at startHost (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/src/host.mjs:144:36) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async waitForActual (node:assert:615:5) + at async strict.rejects (node:assert:738:25) + at async TestContext. (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:298:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7), + operator: 'rejects', + diff: 'simple' + } diff --git a/agents/filbert/work/s4-follow-up-review/r2/mut-G150cb.txt b/agents/filbert/work/s4-follow-up-review/r2/mut-G150cb.txt new file mode 100644 index 00000000..81a17d79 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2/mut-G150cb.txt @@ -0,0 +1,284 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (35.43274ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (33.471014ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (19.140396ms) +✔ decide prints a declining choice as declining (18.602067ms) +✔ an unknown outcome is reported once and never resent (15.932529ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (19.327761ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (20.050555ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (20.37509ms) +✔ every human command refuses inside an agent run before it touches the bus (18.831285ms) +✔ usage errors exit 4; no business and no host is a usage error (16.415544ms) +✔ agents and tasks print through the broker (17.203061ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.089413ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (76.607075ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.134543ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (62.0657ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (62.772908ms) +✔ a business without tracker.baseUrl gets no trackers entry (67.963316ms) +✔ an unknown business and a broken system config refuse with exit 3 (95.843406ms) +✔ empty views say so (1.214257ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.396918ms) +✔ tasks print the tracker fields the snapshot carries (0.243224ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (987.093188ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (160.916513ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (86.8488ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (150.382607ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (139.277378ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (86.910738ms) +✖ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (131.302157ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (95.94533ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (161.85581ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.350341ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.794756ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (219.265381ms) +✔ bus start refuses with exit 3 without a notifier config (98.894125ms) +✔ bus start runs until bus stop; status reports it while it runs (601.796402ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.508715ms) +✔ zoned uses the IANA zone across DST (25.038826ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (30.5017ms) +✔ two blocking decisions get two DMs with different nonces (26.593526ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.386889ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (21.890246ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (32.141384ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.508855ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.87658ms) +✔ 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.40634ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (72.21605ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (18.866204ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.505699ms) +✔ an inbox read failure is logged and the next poll retries (0.872537ms) +✔ no Discord id reaches the journal or the log (12.165089ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.471622ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.196749ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.481381ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.564967ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.689229ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.216716ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.380511ms) +✔ 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.514203ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.416754ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.534148ms) +✔ digest content stays within Discord's 2000 characters (0.30674ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.166482ms) +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.330269ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (55.698068ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2262.563597ms) +✔ busExit and refuseInsideAgent (0.410165ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.05756ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.40958ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.554093ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.499245ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.818583ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (9.924491ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.449452ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.215793ms) +✔ authorize: open channel, listed user (2.060401ms) +✔ authorize: wrong guild (0.227166ms) +✔ authorize: no guild (DM) (0.192796ms) +✔ authorize: unlisted channel (0.215715ms) +✔ authorize: unknown channel, no info (0.212085ms) +✔ authorize: thread of listed parent (0.210089ms) +✔ authorize: thread of unlisted parent (0.272773ms) +✔ authorize: text channel that is not a thread and not listed (0.182438ms) +✔ authorize: unlisted user (0.21741ms) +✔ authorize: no author (0.347694ms) +✔ authorize: bot author (listed id, bot flag) (0.361386ms) +✔ authorize: system author (0.117232ms) +✔ authorize: the bot itself (0.576604ms) +✔ authorize: webhook (0.093925ms) +✔ authorize: mention channel without mention (0.359812ms) +✔ authorize: mention channel with bot mention (0.172123ms) +✔ authorize: mention channel with @everyone only (0.125202ms) +✔ authorize: mention channel mentioning someone else (0.10203ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.076922ms) +✔ authorize: private thread under mention channel, mentioned (0.097288ms) +✔ authorize: private thread under mention channel, not mentioned (0.066618ms) +✔ authorize: thread in another guild per channel info (0.07442ms) +✔ authorize: not an object (0.070275ms) +✔ authorize: no id (0.064442ms) +✔ authorize: oversize content is accepted and flagged (0.068077ms) +✔ authorize: exactly the limit is not oversize (0.063815ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.457044ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (1.571421ms) +✔ binding: a complete binding validates and is frozen (2.585191ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.298511ms) +✔ binding: empty allowlists refuse (0.529734ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.327905ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.706755ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.072711ms) +✔ binding: file must be 0600, regular, not a symlink (1.60354ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.345564ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (119.298929ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.297177ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (453.045135ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (193.040797ms) +✔ cli: run refuses when STOP is present, before any network use (143.421673ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.827187ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.288129ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (16.257283ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.459766ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.603013ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.167039ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.383027ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.762348ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.188128ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.399165ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.987268ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.232049ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.50096ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.925893ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.888828ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.922476ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.85346ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.362162ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.495436ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.72276ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.249211ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.421759ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.878197ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.94634ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (7.098928ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.649806ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.833014ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.923517ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.232272ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.512824ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.608302ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.877487ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.690642ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.471784ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (52.400819ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (60.889153ms) +✔ engine: one prompt, one turn, text and usage come back (54.64165ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (359.311248ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (230.548602ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.941707ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (235.915266ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (132.665735ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.985025ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.129962ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.415791ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.648904ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.61233ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.659735ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.644652ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.915593ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.785218ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.52202ms) +✔ gateway: op 9 resumable resumes (0.356266ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.027665ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (1.409873ms) +✔ gateway: close() is final and unparseable frames are ignored (0.399002ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (66.803683ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (71.644008ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (167.29093ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.473556ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (101.697239ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (100.44163ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (92.056718ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (204.41622ms) +✔ git: push pushes the named branch only and reports up to date (70.68544ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (93.405065ms) +✔ git: the credential helper answers get over https from a private file and nothing else (232.581258ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (843.15864ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.754162ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (88.060157ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.128858ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.404495ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (67.431176ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (394.471285ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.439418ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.151458ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.02478ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (47.189856ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (166.155645ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (87.871317ms) +✔ notices: a kind is recorded per UTC day and found again (0.537014ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.512574ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (4.426554ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.780887ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (52.116742ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (72.489762ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (49.876372ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (103.562466ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (707.493943ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.625649ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.043802ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.705955ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.90072ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.009007ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (8.457109ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.047279ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.271075ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.125634ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.421101ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (18.19222ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (16.147333ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (7.368501ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.717434ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.96856ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.236817ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.490122ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (7.130594ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.449037ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.235061ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.716789ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.22119ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.636101ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.140177ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (6.22213ms) +✔ tools: listing and search caps hold (12.011688ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.933103ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (8.615618ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.388032ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.306203ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.899277ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (4.264264ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.64386ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.849991ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.296687ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1029.398845ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.999759ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.300067ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.960882ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.560185ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 21957.847133 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:271:1 +✖ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (131.302157ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: write EPIPE + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:279:3) + 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: Error: write EPIPE + at epipe (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:113:24) + at file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:276:51 + at ChildProcess.send (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:94:23) + at startHost (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/src/host.mjs:150:36) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async waitForActual (node:assert:615:5) + at async strict.rejects (node:assert:738:25) + at async TestContext. (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:279:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7), + operator: 'rejects', + diff: 'simple' + } diff --git a/agents/filbert/work/s4-follow-up-review/r2/mut-G184.txt b/agents/filbert/work/s4-follow-up-review/r2/mut-G184.txt new file mode 100644 index 00000000..78b6dcf4 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2/mut-G184.txt @@ -0,0 +1,272 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (28.110281ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (32.982672ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (22.885134ms) +✔ decide prints a declining choice as declining (25.145562ms) +✔ an unknown outcome is reported once and never resent (18.564366ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (14.529736ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (22.739403ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.866005ms) +✔ every human command refuses inside an agent run before it touches the bus (21.68454ms) +✔ usage errors exit 4; no business and no host is a usage error (21.78543ms) +✔ agents and tasks print through the broker (15.958803ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.128111ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (61.498678ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.703509ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (68.727509ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.999728ms) +✔ a business without tracker.baseUrl gets no trackers entry (66.904636ms) +✔ an unknown business and a broken system config refuse with exit 3 (78.055529ms) +✔ empty views say so (1.133263ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.419978ms) +✔ tasks print the tracker fields the snapshot carries (0.319808ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (976.122771ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (154.299081ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.053449ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (132.022671ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.105045ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (83.526731ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (129.970312ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (94.771035ms) +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.965173ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (20.839386ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.485229ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (212.911332ms) +✔ bus start refuses with exit 3 without a notifier config (86.673161ms) +✔ bus start runs until bus stop; status reports it while it runs (603.633572ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.236399ms) +✔ zoned uses the IANA zone across DST (26.30102ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (29.059472ms) +✔ two blocking decisions get two DMs with different nonces (23.732182ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.325699ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.461945ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (41.930339ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.338762ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (23.746349ms) +✔ 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.618175ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (48.72618ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.130065ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.446646ms) +✔ an inbox read failure is logged and the next poll retries (0.794638ms) +✔ no Discord id reaches the journal or the log (11.948413ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.403003ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.359839ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.33723ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.528168ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.658175ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.331984ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.416922ms) +✔ 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.599672ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.454245ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.50318ms) +✔ digest content stays within Discord's 2000 characters (0.324091ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.32366ms) +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.63492ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (52.652269ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2262.731962ms) +✔ busExit and refuseInsideAgent (0.487009ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.301618ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.653151ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.724349ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.637314ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.63012ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.249497ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.647039ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.315016ms) +✔ authorize: open channel, listed user (2.126537ms) +✔ authorize: wrong guild (0.221439ms) +✔ authorize: no guild (DM) (0.200742ms) +✔ authorize: unlisted channel (0.228561ms) +✔ authorize: unknown channel, no info (0.289141ms) +✔ authorize: thread of listed parent (0.208926ms) +✔ authorize: thread of unlisted parent (0.258497ms) +✔ authorize: text channel that is not a thread and not listed (0.903678ms) +✔ authorize: unlisted user (0.204827ms) +✔ authorize: no author (0.29672ms) +✔ authorize: bot author (listed id, bot flag) (0.167752ms) +✔ authorize: system author (0.129957ms) +✔ authorize: the bot itself (0.135212ms) +✔ authorize: webhook (0.337836ms) +✔ authorize: mention channel without mention (0.19011ms) +✔ authorize: mention channel with bot mention (0.801285ms) +✔ authorize: mention channel with @everyone only (0.102646ms) +✔ authorize: mention channel mentioning someone else (0.102058ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.98214ms) +✔ authorize: private thread under mention channel, mentioned (0.13949ms) +✔ authorize: private thread under mention channel, not mentioned (0.085569ms) +✔ authorize: thread in another guild per channel info (0.08798ms) +✔ authorize: not an object (0.072785ms) +✔ authorize: no id (1.780671ms) +✔ authorize: oversize content is accepted and flagged (0.117197ms) +✔ authorize: exactly the limit is not oversize (0.083387ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (4.578801ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.172546ms) +✔ binding: a complete binding validates and is frozen (2.608044ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.245052ms) +✔ binding: empty allowlists refuse (0.427015ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.285782ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.69464ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.206475ms) +✔ binding: file must be 0600, regular, not a symlink (1.620486ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.922523ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (124.830716ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.290297ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (400.058344ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (197.066459ms) +✔ cli: run refuses when STOP is present, before any network use (143.70697ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.823845ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.35339ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (18.60528ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.822178ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.917512ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.258073ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.43895ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.700783ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.371522ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (6.756154ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.409746ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.398602ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.991794ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.124461ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.730443ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (7.021588ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.705559ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.109771ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.803502ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.797716ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.964219ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.242547ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.589818ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.172136ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.315022ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.567123ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.837529ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.780926ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.333843ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.559162ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.198925ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.700731ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.607945ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.472954ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (63.364982ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (57.287835ms) +✔ engine: one prompt, one turn, text and usage come back (48.509138ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (353.27459ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.428256ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (115.226251ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.254298ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.68854ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.405945ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.539447ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.400819ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.510602ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.20727ms) +✔ engine: a malformed JSONL line fails the turn, not the process (29.712326ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.98441ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.019012ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.583719ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.672715ms) +✔ gateway: op 9 resumable resumes (0.39255ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.954666ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.538655ms) +✔ gateway: close() is final and unparseable frames are ignored (0.427669ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (118.006678ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (79.171555ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (119.506955ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.428778ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (96.226379ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (86.393572ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (91.503938ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (207.779808ms) +✔ git: push pushes the named branch only and reports up to date (66.795796ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (89.676547ms) +✔ git: the credential helper answers get over https from a private file and nothing else (202.847074ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (762.933327ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.255682ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (96.513437ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.984781ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.856681ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (59.3486ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (365.359776ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.450439ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.720752ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.080843ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (37.222767ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (143.486067ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (91.322222ms) +✔ notices: a kind is recorded per UTC day and found again (0.548648ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.423529ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.424165ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.941037ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.76052ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (58.674063ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.874393ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (70.202692ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (671.226932ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.375117ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.352327ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.78035ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.963883ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.300933ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.665344ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.018848ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.641699ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.256708ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.259264ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.463092ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.77384ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.397298ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.763851ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.762129ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1010.222683ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.449508ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (6.577531ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.282408ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.224874ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.41477ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.857159ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.409446ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.323269ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.344908ms) +✔ tools: listing and search caps hold (9.718902ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.984826ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (4.827668ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (0.958014ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.927539ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.244158ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.188005ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.57282ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.493954ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.252435ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1027.689525ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.353728ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.24388ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.922832ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.266776ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 22121.994867 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:301:1 +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.965173ms) + Error: write EPIPE + at epipe (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:113:24) + at file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:307:71 + at ChildProcess.send (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:94:23) + at Object.close (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/src/host.mjs:184:36) + at TestContext. (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:310:27) + 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) { + code: 'EPIPE', + errno: -32, + syscall: 'write' + } diff --git a/agents/filbert/work/s4-follow-up-review/r2/mut-G188.txt b/agents/filbert/work/s4-follow-up-review/r2/mut-G188.txt new file mode 100644 index 00000000..2cc9822b --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2/mut-G188.txt @@ -0,0 +1,272 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (27.6792ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.880744ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.959497ms) +✔ decide prints a declining choice as declining (22.678202ms) +✔ an unknown outcome is reported once and never resent (31.896262ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (24.190774ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (24.327415ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (28.527623ms) +✔ every human command refuses inside an agent run before it touches the bus (35.050755ms) +✔ usage errors exit 4; no business and no host is a usage error (20.717769ms) +✔ agents and tasks print through the broker (20.022633ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.490166ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (65.55727ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.911656ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (72.557394ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (65.873141ms) +✔ a business without tracker.baseUrl gets no trackers entry (94.2475ms) +✔ an unknown business and a broken system config refuse with exit 3 (126.978258ms) +✔ empty views say so (1.166349ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.296625ms) +✔ tasks print the tracker fields the snapshot carries (0.238392ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1034.160004ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (187.995177ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (80.789725ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (155.937738ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (145.407867ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (78.863961ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (121.500331ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (67.120216ms) +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (133.136947ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.168291ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.37302ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (199.885757ms) +✔ bus start refuses with exit 3 without a notifier config (86.072818ms) +✔ bus start runs until bus stop; status reports it while it runs (595.667615ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.148754ms) +✔ zoned uses the IANA zone across DST (24.10958ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (29.08283ms) +✔ two blocking decisions get two DMs with different nonces (19.642899ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.52952ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.202211ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (26.208091ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (39.261767ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (30.601569ms) +✔ 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 (169.780994ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (90.354432ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.081139ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.763897ms) +✔ an inbox read failure is logged and the next poll retries (0.963014ms) +✔ no Discord id reaches the journal or the log (10.971458ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.440819ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.67175ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.266836ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.533318ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.609676ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.168215ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.422331ms) +✔ 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.561709ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.403015ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.889903ms) +✔ digest content stays within Discord's 2000 characters (0.481024ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.495217ms) +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 (406.619109ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (58.198063ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2305.013766ms) +✔ busExit and refuseInsideAgent (0.43676ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.156934ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.852656ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.599286ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.598493ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.452012ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.157582ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (10.00487ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.190041ms) +✔ authorize: open channel, listed user (1.721486ms) +✔ authorize: wrong guild (0.256044ms) +✔ authorize: no guild (DM) (0.170289ms) +✔ authorize: unlisted channel (0.183946ms) +✔ authorize: unknown channel, no info (0.202628ms) +✔ authorize: thread of listed parent (0.220222ms) +✔ authorize: thread of unlisted parent (0.168494ms) +✔ authorize: text channel that is not a thread and not listed (0.160071ms) +✔ authorize: unlisted user (0.197588ms) +✔ authorize: no author (0.359775ms) +✔ authorize: bot author (listed id, bot flag) (0.183125ms) +✔ authorize: system author (0.139118ms) +✔ authorize: the bot itself (0.081575ms) +✔ authorize: webhook (4.239841ms) +✔ authorize: mention channel without mention (0.262162ms) +✔ authorize: mention channel with bot mention (0.17512ms) +✔ authorize: mention channel with @everyone only (0.092825ms) +✔ authorize: mention channel mentioning someone else (0.107793ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.086932ms) +✔ authorize: private thread under mention channel, mentioned (0.103205ms) +✔ authorize: private thread under mention channel, not mentioned (0.078517ms) +✔ authorize: thread in another guild per channel info (0.079424ms) +✔ authorize: not an object (0.073407ms) +✔ authorize: no id (0.073915ms) +✔ authorize: oversize content is accepted and flagged (0.082392ms) +✔ authorize: exactly the limit is not oversize (0.079356ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.517693ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.157485ms) +✔ binding: a complete binding validates and is frozen (2.3986ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.248444ms) +✔ binding: empty allowlists refuse (0.295712ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.047756ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.486968ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.425124ms) +✔ binding: file must be 0600, regular, not a symlink (1.493459ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.138256ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (113.416015ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.277247ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (485.081892ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (231.468225ms) +✔ cli: run refuses when STOP is present, before any network use (141.532737ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.215475ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.401914ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (15.808592ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.450167ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.340884ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.469275ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.398236ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.354857ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.708396ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.902741ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.244488ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (2.134136ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.824313ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (35.820566ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.738186ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (7.945891ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.162356ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (8.909671ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (5.757391ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.774055ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (9.481754ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.019376ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.369679ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.809673ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (8.844546ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.37508ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.786148ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.756778ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.184268ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.465564ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.124655ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.675076ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.432536ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.43406ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (58.348494ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (83.53097ms) +✔ engine: one prompt, one turn, text and usage come back (71.740014ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (359.019361ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (240.173341ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.968826ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (237.909336ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.244064ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.439979ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.935737ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.365743ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.550151ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.050088ms) +✔ engine: a malformed JSONL line fails the turn, not the process (31.130674ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.179155ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.534997ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.696919ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.563678ms) +✔ gateway: op 9 resumable resumes (0.538881ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.969685ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.542991ms) +✔ gateway: close() is final and unparseable frames are ignored (0.593827ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (103.263465ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (88.988909ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (188.026053ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.455221ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (91.774702ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (106.775486ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (96.639479ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (240.719029ms) +✔ git: push pushes the named branch only and reports up to date (88.693308ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (113.331332ms) +✔ git: the credential helper answers get over https from a private file and nothing else (235.626351ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (770.306757ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.347243ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (113.699179ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (4.612429ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (15.637722ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (76.59014ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (391.490393ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.417385ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.194858ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.399423ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (54.617962ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (171.507477ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (103.182205ms) +✔ notices: a kind is recorded per UTC day and found again (0.527362ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.54451ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.227006ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.935998ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (64.481758ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (66.592143ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (55.259793ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (89.513249ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (778.666623ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.662013ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.982422ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.886994ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.927825ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.229454ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.853504ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.689321ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.184436ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.417793ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.246111ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (15.959131ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.646952ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.356081ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.916843ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.788211ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.573658ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.506526ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (7.248185ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.463524ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.248672ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.630893ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.89569ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (7.302006ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (7.749161ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.631927ms) +✔ tools: listing and search caps hold (14.447928ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.065694ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.237343ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.877633ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.397025ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (7.711414ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (6.683566ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.634625ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.919996ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.459749ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1048.167928ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (5.19401ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.404135ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (3.222274ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.908022ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 22201.031064 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:301:1 +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (133.136947ms) + Error: write EPIPE + at epipe (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:113:24) + at file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:307:71 + at ChildProcess.send (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:94:23) + at Object.close (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/src/host.mjs:188:34) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///home/jwoltje/filbert-scratch/r45b/mut/packages/cli/tests/host.test.mjs:310:16) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + code: 'EPIPE', + errno: -32, + syscall: 'write' + } diff --git a/agents/filbert/work/s4-follow-up-review/r2/mut-X2.txt b/agents/filbert/work/s4-follow-up-review/r2/mut-X2.txt new file mode 100644 index 00000000..6fb0d045 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2/mut-X2.txt @@ -0,0 +1,254 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (31.969738ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (39.737194ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.361087ms) +✔ decide prints a declining choice as declining (17.025637ms) +✔ an unknown outcome is reported once and never resent (15.280986ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.697592ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.24768ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (19.495421ms) +✔ every human command refuses inside an agent run before it touches the bus (17.204145ms) +✔ usage errors exit 4; no business and no host is a usage error (23.921609ms) +✔ agents and tasks print through the broker (19.271732ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.995047ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (71.948522ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.960746ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.340912ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (52.205055ms) +✔ a business without tracker.baseUrl gets no trackers entry (66.571783ms) +✔ an unknown business and a broken system config refuse with exit 3 (98.625237ms) +✔ empty views say so (1.147018ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.389273ms) +✔ tasks print the tracker fields the snapshot carries (0.255955ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (972.10351ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (145.119735ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (72.932258ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (130.878869ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (120.029694ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (74.048653ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (126.357336ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (72.843123ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (151.944814ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.305288ms) +✔ bus stop refuses to signal a live pid that is not a bus host (201.954648ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (202.704511ms) +✔ bus start refuses with exit 3 without a notifier config (87.238125ms) +✔ bus start runs until bus stop; status reports it while it runs (608.466841ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.537352ms) +✔ zoned uses the IANA zone across DST (28.984589ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (35.536375ms) +✔ two blocking decisions get two DMs with different nonces (24.125453ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.373763ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (18.42312ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (25.930443ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.237752ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.716843ms) +✔ 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.048653ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (55.076557ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.252662ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.385345ms) +✔ an inbox read failure is logged and the next poll retries (0.716073ms) +✔ no Discord id reaches the journal or the log (9.917987ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.355804ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.934082ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.760813ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.518039ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.630645ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.149706ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.523351ms) +✔ 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.545512ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.424207ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.566705ms) +✔ digest content stays within Discord's 2000 characters (0.299215ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.755605ms) +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 (363.505078ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (73.510792ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2279.080001ms) +✔ busExit and refuseInsideAgent (0.456201ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.778972ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.395996ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.812142ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.556216ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.278766ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.253301ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.844958ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.646394ms) +✔ authorize: open channel, listed user (3.628704ms) +✔ authorize: wrong guild (0.279293ms) +✔ authorize: no guild (DM) (0.209883ms) +✔ authorize: unlisted channel (0.255814ms) +✔ authorize: unknown channel, no info (0.202225ms) +✔ authorize: thread of listed parent (0.213409ms) +✔ authorize: thread of unlisted parent (0.187026ms) +✔ authorize: text channel that is not a thread and not listed (0.216346ms) +✔ authorize: unlisted user (0.227274ms) +✔ authorize: no author (0.331292ms) +✔ authorize: bot author (listed id, bot flag) (0.207156ms) +✔ authorize: system author (0.146919ms) +✔ authorize: the bot itself (0.154651ms) +✔ authorize: webhook (0.128747ms) +✔ authorize: mention channel without mention (1.013608ms) +✔ authorize: mention channel with bot mention (0.208679ms) +✔ authorize: mention channel with @everyone only (0.091958ms) +✔ authorize: mention channel mentioning someone else (0.115388ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.083698ms) +✔ authorize: private thread under mention channel, mentioned (0.102063ms) +✔ authorize: private thread under mention channel, not mentioned (0.089728ms) +✔ authorize: thread in another guild per channel info (0.096899ms) +✔ authorize: not an object (0.078342ms) +✔ authorize: no id (0.090555ms) +✔ authorize: oversize content is accepted and flagged (0.093124ms) +✔ authorize: exactly the limit is not oversize (0.091879ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (1.749184ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.201346ms) +✔ binding: a complete binding validates and is frozen (3.314113ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.497101ms) +✔ binding: empty allowlists refuse (0.514452ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.452897ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.929276ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.65795ms) +✔ binding: file must be 0600, regular, not a symlink (1.616536ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.496105ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (109.359566ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.89336ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (388.837995ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (196.775336ms) +✔ cli: run refuses when STOP is present, before any network use (142.039635ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.862469ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.219886ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (18.186298ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.329088ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.6665ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.423504ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.988764ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.021807ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.307203ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.48659ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.089687ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.421299ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.959944ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.531392ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.397552ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.835294ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.870664ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.479833ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.851379ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.691169ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (3.938465ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.447954ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (8.513208ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.206445ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.632221ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.147612ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.405469ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.941083ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.22764ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.702662ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.358136ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.778935ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.899188ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.500534ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (57.798533ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (53.6384ms) +✔ engine: one prompt, one turn, text and usage come back (49.275626ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (363.217666ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (232.497833ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.01316ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.975647ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.562214ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.472191ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.99676ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.356989ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.493947ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.611539ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.344975ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.707241ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.882691ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.786249ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.511608ms) +✔ gateway: op 9 resumable resumes (0.319292ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.778143ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.642035ms) +✔ gateway: close() is final and unparseable frames are ignored (0.559967ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (67.188729ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (61.78748ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (139.799339ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.355388ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (97.80143ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.378324ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.695407ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (206.221662ms) +✔ git: push pushes the named branch only and reports up to date (73.367078ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (94.269428ms) +✔ git: the credential helper answers get over https from a private file and nothing else (192.763263ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (769.764726ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.897571ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (85.347714ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.147182ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.246329ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (62.221547ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (334.143313ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.446838ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.262358ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.024871ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (52.737529ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (149.618132ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (90.73196ms) +✔ notices: a kind is recorded per UTC day and found again (0.498273ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.963305ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.151807ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.873923ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (49.5515ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (70.03735ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (45.676045ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.469176ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (652.571542ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.686902ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.134727ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.682434ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.745166ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.559807ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.585034ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.211948ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.80702ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.746831ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.790283ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.152569ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.555869ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.068422ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.515899ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.304347ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.961515ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.445493ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (5.98185ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.287105ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.287308ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.314603ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.155744ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.39861ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.830748ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.451569ms) +✔ tools: listing and search caps hold (11.761112ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.869105ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (5.278796ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.463513ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.651639ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.2606ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.813397ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.691092ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.533712ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.257832ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1026.999287ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (4.232515ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.234823ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.520515ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.318613ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 244 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3147.70154 diff --git a/agents/filbert/work/s4-follow-up-review/r2/mut-Y5.txt b/agents/filbert/work/s4-follow-up-review/r2/mut-Y5.txt new file mode 100644 index 00000000..459edf83 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/r2/mut-Y5.txt @@ -0,0 +1,254 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (38.515044ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (47.51329ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (35.964821ms) +✔ decide prints a declining choice as declining (31.054389ms) +✔ an unknown outcome is reported once and never resent (34.197423ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (29.309979ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (27.556839ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (20.37102ms) +✔ every human command refuses inside an agent run before it touches the bus (26.169002ms) +✔ usage errors exit 4; no business and no host is a usage error (24.956371ms) +✔ agents and tasks print through the broker (22.68945ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.294562ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (110.117412ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (72.507604ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (69.246247ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (73.220899ms) +✔ a business without tracker.baseUrl gets no trackers entry (62.09265ms) +✔ an unknown business and a broken system config refuse with exit 3 (127.168522ms) +✔ empty views say so (1.295782ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.558117ms) +✔ tasks print the tracker fields the snapshot carries (0.274578ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1083.595282ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (157.786013ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (84.370106ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (151.588533ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (132.118094ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (90.212014ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (138.666859ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (76.413159ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (152.540274ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.882033ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.523578ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (214.154171ms) +✔ bus start refuses with exit 3 without a notifier config (91.819312ms) +✔ bus start runs until bus stop; status reports it while it runs (600.302334ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.988091ms) +✔ zoned uses the IANA zone across DST (30.219864ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (45.97592ms) +✔ two blocking decisions get two DMs with different nonces (31.097482ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.475183ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (30.354281ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (42.400269ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (42.51722ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (31.319376ms) +✔ 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 (186.860009ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (73.201094ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (19.161185ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (14.583386ms) +✔ an inbox read failure is logged and the next poll retries (1.144795ms) +✔ no Discord id reaches the journal or the log (13.866465ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (3.000712ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.714193ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.019339ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.035753ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.194497ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.029101ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.618274ms) +✔ 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.822849ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.729591ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.728135ms) +✔ digest content stays within Discord's 2000 characters (0.426944ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.185591ms) +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 (457.565158ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (98.483208ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2314.080557ms) +✔ busExit and refuseInsideAgent (0.439887ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.573033ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.664955ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.640464ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.586251ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.427045ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (9.302078ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (11.289325ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.268997ms) +✔ authorize: open channel, listed user (2.554353ms) +✔ authorize: wrong guild (0.28107ms) +✔ authorize: no guild (DM) (0.235239ms) +✔ authorize: unlisted channel (0.241547ms) +✔ authorize: unknown channel, no info (0.48956ms) +✔ authorize: thread of listed parent (0.229009ms) +✔ authorize: thread of unlisted parent (0.191603ms) +✔ authorize: text channel that is not a thread and not listed (0.241444ms) +✔ authorize: unlisted user (0.228935ms) +✔ authorize: no author (0.390509ms) +✔ authorize: bot author (listed id, bot flag) (0.182746ms) +✔ authorize: system author (0.124397ms) +✔ authorize: the bot itself (0.114231ms) +✔ authorize: webhook (0.095541ms) +✔ authorize: mention channel without mention (0.168843ms) +✔ authorize: mention channel with bot mention (0.186388ms) +✔ authorize: mention channel with @everyone only (0.096925ms) +✔ authorize: mention channel mentioning someone else (0.0998ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.126861ms) +✔ authorize: private thread under mention channel, mentioned (0.109268ms) +✔ authorize: private thread under mention channel, not mentioned (0.072714ms) +✔ authorize: thread in another guild per channel info (0.083146ms) +✔ authorize: not an object (0.068098ms) +✔ authorize: no id (0.078725ms) +✔ authorize: oversize content is accepted and flagged (0.253538ms) +✔ authorize: exactly the limit is not oversize (0.08734ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.502306ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.178409ms) +✔ binding: a complete binding validates and is frozen (5.486775ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.690813ms) +✔ binding: empty allowlists refuse (0.498903ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.384675ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (5.152686ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (4.848345ms) +✔ binding: file must be 0600, regular, not a symlink (2.928459ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.729775ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (160.649256ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.911376ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (509.310391ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (202.124231ms) +✔ cli: run refuses when STOP is present, before any network use (139.011262ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.821345ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.236177ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (21.153238ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (25.874609ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.40952ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.442152ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.606294ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.259807ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.30013ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.434223ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.549185ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.71826ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.24311ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.852768ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (10.176051ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.523834ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.257067ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.504876ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.378369ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.912003ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.883532ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.912688ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.724385ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.950304ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (8.224905ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.842945ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.86143ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.031171ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.363979ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.527863ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.648135ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.774414ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.081553ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.447159ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (83.482212ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (69.63021ms) +✔ engine: one prompt, one turn, text and usage come back (66.678912ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (371.782008ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (241.448069ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.063287ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (235.449723ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (132.246946ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (213.968925ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.802282ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.604928ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.477462ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.944274ms) +✔ engine: a malformed JSONL line fails the turn, not the process (27.316305ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (48.364427ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (4.279329ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.781946ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.844029ms) +✔ gateway: op 9 resumable resumes (0.794967ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.980634ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.601975ms) +✔ gateway: close() is final and unparseable frames are ignored (0.510457ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (129.789886ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (95.795626ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (175.073445ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.51705ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (126.236491ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (106.409214ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (97.66704ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (218.75961ms) +✔ git: push pushes the named branch only and reports up to date (75.659369ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (89.02577ms) +✔ git: the credential helper answers get over https from a private file and nothing else (238.364192ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (820.916871ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (7.958684ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (145.127968ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.405799ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (7.574459ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (71.328509ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (410.144251ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.47381ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.138192ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.033196ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (38.865292ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (155.465209ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (92.851609ms) +✔ notices: a kind is recorded per UTC day and found again (0.486171ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.834449ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.46726ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (3.111891ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (61.040695ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (58.886697ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (59.261987ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (133.344212ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (724.530023ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.110496ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.27471ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.739302ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.116865ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.085608ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.424077ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.591393ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.064091ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.563102ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.494481ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.46439ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.274876ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.772509ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.188447ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.576528ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.330689ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.588853ms) +✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (7.347286ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.475812ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.246317ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.542402ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.711917ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.023525ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.216785ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (6.605458ms) +✔ tools: listing and search caps hold (18.317701ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.106927ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (14.520231ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.964065ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (3.231932ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (15.981332ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (5.058494ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (5.061403ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.173935ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.915134ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1042.252584ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (5.039832ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.257477ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (2.80953ms) +✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (2.249446ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 244 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3396.033758 diff --git a/agents/filbert/work/s4-follow-up-review/review-r2.md b/agents/filbert/work/s4-follow-up-review/review-r2.md new file mode 100644 index 00000000..71579b01 --- /dev/null +++ b/agents/filbert/work/s4-follow-up-review/review-r2.md @@ -0,0 +1,164 @@ +# Row 45, S4 follow-up, round 2 review (Filbert) + +Issue #1527, request comment 26882, queue rev 216 (`19dcc553`). Packet: +`agents/rocko/work/s4-follow-up/` at `af1377d7`, BUILD.md "Round 2". +Candidate: `candidate-manifest.sha256` sha256 +`5b067a9dad645c31d99e1ea02575cd2fc1ba547ce011ea81c56b17ae29da0d0e`, 8 +files, over `d539d8d2` with `build.patch` sha256 +`c8cec070da60d8297f1ee5b006a1099ab9376fd3abf32fa4461f967750ad6756`. +Rulings: lead decisions 72 and 73 (comment 26879). Second reviewer: +Darkwing, round 2 approve (comment 26884). +This verdict: comment 26886. + +Verdict: **approve.** B1 and R1 are fixed and tested. I agree with the +declined `append` type check, and my round 1 note 1 is withdrawn. G184 +fails in my harness; it doesn't hang. Everything below the verdict is a note. + +## Method + +- Detached worktrees at `d539d8d2` under `~/filbert-scratch/r45b/`: a + candidate tree and a mutant tree. `git apply build.patch`, then + `sha256sum -c`: 8 OK in both, and 8 OK in the mutant tree after the run. +- `gate.sh` (it now takes the output directory as a third argument) runs + every package's node tests and every `scripts/test-*.sh`, one at a time, + and tees each output. `DOCKER_HOST` points at a socket that doesn't + exist. 14:13:50Z to 14:17:19Z, load 3.08 to 3.27. I didn't rerun the base + this round: round 1 has it, and the only base change since is row 46. +- `probe/probe.test.mjs` (round 1, unchanged) and `probe/probe-r2.test.mjs` + drive `createNotifier` with a fake clock and a fake Discord. +- `probe/append-check.test.mjs` adds a `badField` check to `append` in the + mutant tree (a temporary edit, restored before the mutant run) and feeds + it a numeric `messageId`. +- `mutants-r2.sh`: Rocko's 31 verbatim, my round 1 X set where it still + applies, and Y1 to Y6 on the new code. It records the wall time of each + run, so a hang shows. A run counts as killed when a test fails or is + cancelled, or the 900 s outer timeout fires. +- Node 24.21.0: `node:24`, no network, the tree mounted read-only, the host + uid. +- `host.test.mjs` five times in a row, to look for flakes in the new EPIPE + tests. + +## Suites + +| Suite | Candidate | +|---|---| +| node cli | 66/66 | +| node cli + discord (Node 24.21.0) | 244/244 | +| node conversation | 152/152 | +| node discord | 178/178 | +| node bus, business, control-board, ledger, mosaic, queue, seat, tasks, webui | all green | +| test-auth, conductor, config, discord, extension-package, foundation, queue, release | all green | +| test-task | 26 pass, 2 fail | +| host.test.mjs ×5 | 15/15 each run, 3.1 to 3.7 s | + +test-task fails "user recall run succeeds" and "recalled user name", the +same two Docker live-recall cases as round 1 and as Rocko's gate. The +conversation failures from round 1 are gone now that row 46 has landed. + +## B1 / decision 73: resolved + +| Probe | Result | +|---|---| +| T1 permanent 403, poll every `POLL_MS` | sends at 0, 30, 60, 90, 120 min; gave-up at 120 | +| T2 binding fixed at 60 min | refused, refused, confirmed (3 sends) | +| T3 restart right after a refusal | no early send | +| T6 crash loop: a fresh notifier every 15 s, one tick each | sends at 0, 30, 60, 90, 120; gave-up at 120 | +| T7 binding fixed at 100 min | refused ×4, then confirmed at 120 | +| T9 permanent 401 | same pace as a 403; gave-up at 120 | +| T4 permanent 500 | 17 sends, no gave-up (unchanged, unlimited) | +| T5 digest 403 | 7 sends in an hour, no gave-up (unchanged) | + +T6 is the case that worried me most in round 1, since the unit restarts +after 15 s. `refusedAt` comes from the journal, so the crash loop keeps the +exact schedule. Five refusals now span two hours, which is what decision 72 +chose five for. The two new tests match what Sage asked for. Mutants Y1 +(keep the first refusal's time), Y2 (every failure waits the cap), Y3 (the +tick gate uses 30 s), Y4 (`<=` in the gate), R2a, R2b and R2c are all +killed. + +## R1: resolved + +`host.mjs:144`, `:150` and `:188` send `close` with a callback behind the +`broker.connected` guard, and `:184` sends `stop` with a callback. The three +EPIPE tests use `failSends`, which traps `child.send` and calls the callback +with `EPIPE` (or emits on the next tick if there's no callback). That +reaches the window deterministically, which my round 1 probe could only do +by blocking the event loop. Dropping any of the four callbacks fails a test +(G144cb, G150cb, G184, G188). Five back-to-back runs of `host.test.mjs` +passed 15/15 each. + +## G184 fails, not hangs + +In my harness G184 is killed by "close() whose stop and close sends fail +with EPIPE still finishes, with exit 1", which fails in 138 ms. The whole +run takes 22 s, and so do G144cb, G150cb and G188 (22 to 23 s), against +3 to 4 s for every other mutant. The extra 20 s is `CLOSE_TIMEOUT_MS` +(20000) running out in the mutated `close()`, which then returns. The +900 s outer timeout never fired. Rocko's cleanup fix holds. + +## The declined `append` type check: agree + +Probe A3 puts my round 1 suggestion into the mutant tree and has Discord +return `messageId: 123`. In 10 minutes the DM was really sent 20 times, and +the journal holds 20 `unknown` lines. It's worse than Rocko described: on +success `attempt` deletes the backoff before `append`, so the throw lands +in the catch with `n = 0`, and the DM repeats on every 30 s poll. The +open-time check (exit 3, the unit stays down) is the loud failure, and +it's the better one. I withdraw round 1 note 1. + +## Mutants + +All 31 of Rocko's are killed under my harness, each by a failing test. + +| Mutant | Result | +|---|---| +| N2, N4, N5, M28, G144, G150, F2a to F2f, J4a to J4d, E1, E2 | killed | +| G144cb, G150cb, G184, G188 | killed, 22 to 23 s (see above) | +| R2a, R2b, R2c, X9, X14, D3, N1a, N1b, N1c | killed | +| X1, X3 to X8, X10 to X13 | killed | +| X2 a 5xx refusal counts | survived; equivalent, `rest.mjs` never refuses with a 5xx | +| Y1 to Y4, Y6 | killed | +| Y5 `definite` drops the `kind === "dm"` check | **survived**; test gap, see note 3 | + +X9 and X14 survived round 1 and are killed now. + +## Notes (not blocking) + +1. **A refusal line dated in the future holds the DM silently.** Probe T8 + writes a 403 line one hour ahead of the clock (a clock stepped back, or a + line written with a fast clock). The DM waits until that line's `at` + plus 30 min, 90 minutes from now, and nothing is logged. It's the safe + direction and an edge case. Darkwing's note 2 is the same finding. +2. **README wording** (`packages/cli/README.md:162-177`): + - It calls 429 an `unknown` outcome, but a 429 is journaled as + `refused`; it just isn't definite. + - It doesn't say what a refused digest waits. It doubles, like an + unknown, with no limit (Darkwing's note 1). + - The bullet at :166 wraps early: "Every retry" sits alone on a short + line. +3. **Y5 test gap.** If `definite` stopped checking `kind`, a digest 403 + would wait the full 30 min instead of doubling, and no test notices. + The behaviour then would be acceptable, so this matters only if the + doubling for digests is meant. One assertion would pin it. +4. **X2 is equivalent**, as in round 1. + +## Darkwing's review (comment 26884) + +Darkwing approves too, and we agree on B1/R2, R1 and the `append` decline. +Their restart sweep covers more restart times than my T3 and T6. Their +note 1 is part of my note 2, and their note 2 is my note 1. Their note 3 +(a journal directory path that is a regular file gets the mode message) is +fair; I didn't probe it. + +## Files + +- `gate.sh`, `mutants-r2.sh`, `probe/probe-r2.test.mjs`, + `probe/append-check.test.mjs` +- Output: + - `r2-gate-exits.txt`, `r2-gate-start.txt` + - `r2-mut-summary.txt`, `r2-mut-time.txt`, and `r2/mut-*.txt` for + G144cb, G150cb, G184, G188, X2 and Y5 (the rest are in the summary) + - `r2-probe.txt`, `r2-append-check.txt` + - `r2-node24.txt`, `r2-host-reps.txt` + - `r2-manifest-mut-after.txt` + - `r2-cand-*.txt` (each suite, teed)