docs(review): row 45 round 2 review record, approve (filbert)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Sequential gate; $1 = tree, $2 = out prefix. Each output teed to a file.
|
# Sequential gate; $1 = tree, $2 = out prefix, $3 = out dir (optional). Each output teed to a file.
|
||||||
T="$1"; P="$2"; O=~/filbert-scratch/r45/out; cd "$T"
|
T="$1"; P="$2"; O="${3:-$HOME/filbert-scratch/r45/out}"; cd "$T"
|
||||||
for d in packages/*/tests; do
|
for d in packages/*/tests; do
|
||||||
p=$(basename "$(dirname "$d")")
|
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
|
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
|
||||||
|
|||||||
@@ -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 <tree> <outdir>
|
||||||
|
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 <dataRoot>\/notify\/<business>[^\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/'
|
||||||
@@ -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(","));
|
||||||
|
});
|
||||||
@@ -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");
|
||||||
|
});
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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 <socket> 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 <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (28.792892ms)
|
||||||
|
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
OK status with missing harness credential exits 3 and still lists accounts
|
||||||
|
OK status reports harness credential (read-only) + mosaic accounts
|
||||||
|
OK api key material never reaches output
|
||||||
|
OK oauth token material never reaches output
|
||||||
|
OK unparseable credential file exits 2
|
||||||
|
OK symlinked credential file exits 4
|
||||||
|
OK env-side credential names reported
|
||||||
|
OK env var values never reach output
|
||||||
|
OK accounts without an accounts dir reports none and creates nothing
|
||||||
|
OK accounts lists files and marks the active one
|
||||||
|
OK loose account perms flagged in listing
|
||||||
|
OK agent --auth with missing account file refuses (exit 4)
|
||||||
|
OK agent --auth with non-0600 account file refuses
|
||||||
|
OK agent --auth with invalid account name refuses
|
||||||
|
OK auth.sh without valid config refuses
|
||||||
|
|
||||||
|
selftest: 15 passed, 0 failed
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
Note: switching to '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 <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
Not currently on any branch.
|
||||||
|
nothing to commit, working tree clean
|
||||||
|
Note: switching to '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 <new-branch-name>
|
||||||
|
|
||||||
|
Or undo this operation with:
|
||||||
|
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
Turn off this advice by setting config variable advice.detachedHead to false
|
||||||
|
|
||||||
|
OK dry-run: allowed change, exit 0, nothing committed (exit 0)
|
||||||
|
OK dry-run committed nothing
|
||||||
|
OK apply: allowed change exits 0 (exit 0)
|
||||||
|
OK apply: attribution in commit subject
|
||||||
|
OK apply: target tree clean after commit
|
||||||
|
OK disallowed path refused (exit 1)
|
||||||
|
OK disallowed path: target untouched
|
||||||
|
OK syntax gate refused broken .mjs (exit 1)
|
||||||
|
OK syntax gate: target untouched
|
||||||
|
OK suite failure refused (exit 1)
|
||||||
|
OK suite failure: target reverted to clean
|
||||||
|
OK disabled policy refused (exit 2)
|
||||||
|
OK disabled policy: target untouched
|
||||||
|
OK failed run refused (exit 1)
|
||||||
|
OK failed run: target untouched
|
||||||
|
OK missing run exits 4 (exit 4)
|
||||||
|
OK invalid policy exits 2 (exit 2)
|
||||||
|
|
||||||
|
selftest: 17 passed, 0 failed
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
OK absent adapter defaults to pi
|
||||||
|
OK adapter mock validates (exit 0)
|
||||||
|
OK unsupported adapter exits 2 (exit 2)
|
||||||
|
OK env exports adapter
|
||||||
|
OK bootstrap creates default when absent (exit 0)
|
||||||
|
OK bootstrap wrote config file
|
||||||
|
OK bootstrap is idempotent on existing config (exit 0)
|
||||||
|
OK bootstrap did not rewrite existing config
|
||||||
|
OK validate missing config exits 3 (exit 3)
|
||||||
|
OK malformed JSON exits 2 (exit 2)
|
||||||
|
OK unsupported configVersion exits 2 (exit 2)
|
||||||
|
OK unknown top-level key exits 2 (exit 2)
|
||||||
|
OK unknown execution key exits 2 (exit 2)
|
||||||
|
OK unsupported backend exits 2 (exit 2)
|
||||||
|
OK unsupported environment exits 2 (exit 2)
|
||||||
|
OK relative dataRoot exits 2 (exit 2)
|
||||||
|
OK non-canonical dataRoot exits 2 (exit 2)
|
||||||
|
OK filesystem root dataRoot exits 2 (exit 2)
|
||||||
|
OK home directory dataRoot exits 2 (exit 2)
|
||||||
|
OK dataRoot containing config dir exits 2 (exit 2)
|
||||||
|
OK control character in provider exits 2 (exit 2)
|
||||||
|
OK symlinked config file exits 2 (exit 2)
|
||||||
|
OK env exports resolve correctly
|
||||||
|
OK failed validation modified nothing
|
||||||
|
|
||||||
|
selftest: 24 passed, 0 failed
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
toolchain: node v26.8.1
|
||||||
|
|
||||||
|
OK syntax: packages/discord/src/approvals.mjs
|
||||||
|
OK syntax: packages/discord/src/authorize.mjs
|
||||||
|
OK syntax: packages/discord/src/binding.mjs
|
||||||
|
OK syntax: packages/discord/src/cli.mjs
|
||||||
|
OK syntax: packages/discord/src/connector.mjs
|
||||||
|
OK syntax: packages/discord/src/context.mjs
|
||||||
|
OK syntax: packages/discord/src/engine-pi.mjs
|
||||||
|
OK syntax: packages/discord/src/errors.mjs
|
||||||
|
OK syntax: packages/discord/src/gateway.mjs
|
||||||
|
OK syntax: packages/discord/src/git.mjs
|
||||||
|
OK syntax: packages/discord/src/journal.mjs
|
||||||
|
OK syntax: packages/discord/src/notify.mjs
|
||||||
|
OK syntax: packages/discord/src/rest.mjs
|
||||||
|
OK syntax: packages/discord/src/setspark.mjs
|
||||||
|
OK syntax: packages/discord/src/tools.mjs
|
||||||
|
OK syntax: packages/discord/src/web.mjs
|
||||||
|
OK syntax: packages/discord/bin/git-credential.mjs
|
||||||
|
OK syntax: packages/discord/extension/tools.mjs
|
||||||
|
OK syntax: packages/discord/tests/approvals.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/authorize.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/binding.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/connector.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/context.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/engine.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/fake-pi.mjs
|
||||||
|
OK syntax: packages/discord/tests/gateway.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/git.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/helpers.mjs
|
||||||
|
OK syntax: packages/discord/tests/journal.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/notify.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/recover.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/rest.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/setspark.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/tools.test.mjs
|
||||||
|
OK syntax: packages/discord/tests/web.test.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/claim-worker.mjs
|
||||||
|
OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs
|
||||||
|
OK syntax: scripts/discord.sh
|
||||||
|
OK syntax: scripts/discord-service.sh
|
||||||
|
OK packages/discord declares no dependencies
|
||||||
|
OK no bot-token-shaped string in packages/discord
|
||||||
|
OK fixture binding uses placeholder ids only
|
||||||
|
OK fixture binding validates
|
||||||
|
OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool
|
||||||
|
OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start
|
||||||
|
OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root
|
||||||
|
OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token
|
||||||
|
OK real pi with protocol vault adds reserve_id to the git verbs
|
||||||
|
OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key
|
||||||
|
OK real pi refuses a git key on a read-only root (fail closed)
|
||||||
|
OK real pi with the pilot flags (--no-tools) exposes no tool at all
|
||||||
|
OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed)
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test packages/discord/tests/ (ℹ pass 178)
|
||||||
|
OK scripts/discord.sh --help exits 0
|
||||||
|
OK scripts/discord.sh check without a binding exits 4
|
||||||
|
OK scripts/discord.sh recover without a binding exits 4
|
||||||
|
OK scripts/discord.sh reload without a binding exits 4
|
||||||
|
OK scripts/discord-service.sh without a command exits 4
|
||||||
|
OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP
|
||||||
|
OK service install writes the rendered unit (0644) and leaves no temp file
|
||||||
|
OK service install a second time reports unchanged
|
||||||
|
OK systemd-analyze verify accepts the rendered unit
|
||||||
|
OK service uninstall removes the unit file
|
||||||
|
OK service install with an unknown flag exits 4
|
||||||
|
OK service install with USER unset finishes and names the account for lingering
|
||||||
|
|
||||||
|
discord suite: 66 passed, 0 failed
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
OK initial ordinary-file install
|
||||||
|
OK installed tree matches canonical source
|
||||||
|
OK installed tree has no symlinks
|
||||||
|
OK check detects installation drift
|
||||||
|
OK sync refuses to overwrite installation drift
|
||||||
|
OK check detects an extra destination file
|
||||||
|
OK check detects an extra destination directory
|
||||||
|
OK check rejects a destination symlink
|
||||||
|
OK sync accepts a canonical source update
|
||||||
|
OK updated installation matches canonical source
|
||||||
|
scripts/test-extension-package.sh: line 14: 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
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0
|
||||||
|
|
||||||
|
OK syntax: scripts/foundation-inspect.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.mjs
|
||||||
|
OK syntax: scripts/foundation/validate-record.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures/build-fixtures.mjs
|
||||||
|
OK syntax: scripts/foundation/canonical.test.mjs
|
||||||
|
OK syntax: scripts/foundation/cli.test.mjs
|
||||||
|
OK syntax: scripts/foundation/fixtures.test.mjs
|
||||||
|
OK syntax: scripts/foundation/resolve.test.mjs
|
||||||
|
OK syntax: scripts/foundation/strict-json.test.mjs
|
||||||
|
OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written)
|
||||||
|
OK fixture generator runs
|
||||||
|
OK checked-in fixtures/bundles equal a fresh generation
|
||||||
|
OK checked-in fixtures/raw equal a fresh generation
|
||||||
|
OK checked-in fixtures/index.json equal a fresh generation
|
||||||
|
OK checked-in demo bundles equal a fresh generation
|
||||||
|
OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT
|
||||||
|
OK node --test scripts/foundation/ (ℹ pass 80)
|
||||||
|
OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)
|
||||||
|
platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999)
|
||||||
|
node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases)
|
||||||
|
schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
|
||||||
|
profile column (schema-valid records only): profile-valid 510, profile-invalid 30
|
||||||
|
profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns
|
||||||
|
OK oracle: zero schema-column disagreements with the pinned checker
|
||||||
|
OK oracle: strict-only profile refusals are counted and asserted
|
||||||
|
OK demo: permitted read preview exits 0 (exit 0)
|
||||||
|
OK demo: permitted file.change preview exits 0 (exit 0)
|
||||||
|
OK demo: assignment.change proposal is unresolved (exit 3) (exit 3)
|
||||||
|
OK demo: revoked registration is refused (exit 3) (exit 3)
|
||||||
|
OK demo: message is not authority (exit 3) (exit 3)
|
||||||
|
OK usage: no arguments exits 2 (exit 2)
|
||||||
|
OK io: missing file exits 4 (exit 4)
|
||||||
|
OK io: directory exits 4 (exit 4)
|
||||||
|
OK io: symlink exits 4 (O_NOFOLLOW) (exit 4)
|
||||||
|
OK bound: oversize fixture exits 2 (exit 2)
|
||||||
|
OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2)
|
||||||
|
OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2)
|
||||||
|
OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0)
|
||||||
|
OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed
|
||||||
|
OK text output starts with the disclaimer
|
||||||
|
OK json output is valid JSON with result allowed and exactly the charter §7 fields
|
||||||
|
OK json golden matches byte-for-byte
|
||||||
|
OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal
|
||||||
|
OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs
|
||||||
|
OK canary never printed (bundle run and credential-file run)
|
||||||
|
OK a non-bundle JSON file is refused at the shape gate, not read into output
|
||||||
|
OK no field of the non-bundle file is echoed
|
||||||
|
|
||||||
|
selftest: 44 passed, 0 failed
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
toolchain: node v26.8.1, git version 2.55.0
|
||||||
|
|
||||||
|
OK syntax: packages/queue/src/cli.mjs
|
||||||
|
OK syntax: packages/queue/src/errors.mjs
|
||||||
|
OK syntax: packages/queue/src/io.mjs
|
||||||
|
OK syntax: packages/queue/src/lock.mjs
|
||||||
|
OK syntax: packages/queue/src/queue.mjs
|
||||||
|
OK syntax: packages/queue/src/review.mjs
|
||||||
|
OK syntax: packages/queue/src/store.mjs
|
||||||
|
OK syntax: packages/queue/tests/commit.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/data.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/dispatch.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/helpers.mjs
|
||||||
|
OK syntax: packages/queue/tests/lock.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/migration.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/review.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/store.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/write.test.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/kill-at.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/lock-child.mjs
|
||||||
|
OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh
|
||||||
|
OK syntax: scripts/queue-commit.sh
|
||||||
|
OK syntax: scripts/git-hooks/pre-commit
|
||||||
|
OK syntax: scripts/mosaic
|
||||||
|
OK queue-commit.sh, the guard and scripts/mosaic are executable
|
||||||
|
OK packages/queue declares no dependencies
|
||||||
|
ℹ tests 148
|
||||||
|
ℹ pass 148
|
||||||
|
ℹ fail 0
|
||||||
|
OK node --test packages/queue/tests/
|
||||||
|
OK scripts/mosaic queue help
|
||||||
|
skip queue verify and render --check: this checkout (/home/jwoltje/filbert-scratch/r45b/cand) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
|
||||||
|
|
||||||
|
queue suite: 27 passed, 0 failed
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
OK valid RELEASE resolves (exit 0)
|
||||||
|
OK invalid RELEASE exits 1 (exit 1)
|
||||||
|
OK missing RELEASE exits 1 (exit 1)
|
||||||
|
OK valid RELEASE leaves image tag consistent with version
|
||||||
|
skip state-machine cases (docker daemon unavailable)
|
||||||
|
|
||||||
|
selftest: 4 passed, 0 failed
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
OK valid task validates (exit 0)
|
||||||
|
OK unknown task key exits 2 (exit 2)
|
||||||
|
OK unsupported taskVersion exits 2 (exit 2)
|
||||||
|
OK invalid task id exits 2 (exit 2)
|
||||||
|
OK empty prompt exits 2 (exit 2)
|
||||||
|
OK NUL in expectExact exits 2 (exit 2)
|
||||||
|
OK out-of-range timeout exits 2 (exit 2)
|
||||||
|
OK missing mission file exits 4 (exit 4)
|
||||||
|
OK task with valid mission validates (exit 0)
|
||||||
|
OK invalid mission exits 2 (exit 2)
|
||||||
|
OK validate missing task exits 4 (exit 4)
|
||||||
|
OK validation does not modify the task file
|
||||||
|
OK prune dry-run exits 0 (exit 0)
|
||||||
|
OK dry-run deleted nothing
|
||||||
|
OK prune --keep=2 --yes removes oldest (exit 0)
|
||||||
|
OK kept exactly 2 newest runs
|
||||||
|
OK newest run kept, oldest pruned
|
||||||
|
OK append-only receipt written (3 entries)
|
||||||
|
OK sessions/workspaces untouched by prune
|
||||||
|
OK prune with invalid keep exits 4 (exit 4)
|
||||||
|
skip adapter seam cases (docker daemon unavailable)
|
||||||
|
skip workspace/capability cases (docker daemon unavailable)
|
||||||
|
skip live task cases (docker unavailable)
|
||||||
|
OK onboard without name exits 4 (non-interactive) (exit 4)
|
||||||
|
OK onboard --name renders profile (exit 0)
|
||||||
|
OK profile written
|
||||||
|
OK canon structure: required filled, optional placeholdered
|
||||||
|
OK canon sections present
|
||||||
|
FAIL user recall run succeeds (exit 1)
|
||||||
|
FAIL recalled user name (response: )
|
||||||
|
OK no agent identity on headless run
|
||||||
|
|
||||||
|
selftest: 26 passed, 2 failed
|
||||||
@@ -0,0 +1,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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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): <root>/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
|
||||||
@@ -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.<anonymous> (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.<anonymous> (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'
|
||||||
|
}
|
||||||
@@ -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.<anonymous> (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.<anonymous> (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'
|
||||||
|
}
|
||||||
@@ -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.<anonymous> (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'
|
||||||
|
}
|
||||||
@@ -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.<anonymous> (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'
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user