docs(review): row 45 round 1 review record, changes (filbert)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sequential gate; $1 = tree, $2 = out prefix. Each output teed to a file.
|
||||||
|
T="$1"; P="$2"; O=~/filbert-scratch/r45/out; cd "$T"
|
||||||
|
for d in packages/*/tests; do
|
||||||
|
p=$(basename "$(dirname "$d")")
|
||||||
|
env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-filbert-r45.sock timeout 900 node --test "packages/$p/tests/*.test.mjs" 2>&1 | tee "$O/$P-node-$p.txt" > /dev/null
|
||||||
|
echo "$P node-$p exit ${PIPESTATUS[0]} $(grep -E '^# (pass|fail|cancelled)' "$O/$P-node-$p.txt" | tr '\n' ' ')"
|
||||||
|
done
|
||||||
|
for s in scripts/test-*.sh; do
|
||||||
|
n=$(basename "$s" .sh); n=${n#test-}
|
||||||
|
env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-filbert-r45.sock timeout 900 bash "$s" 2>&1 | tee "$O/$P-suite-$n.txt" > /dev/null
|
||||||
|
echo "$P suite-$n exit ${PIPESTATUS[0]} load $(cut -d" " -f1 /proc/loadavg)"
|
||||||
|
done
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Row 45 round 1 mutants (Filbert): Rocko's 18 (agents/rocko/work/s4-follow-up/mutants.sh)
|
||||||
|
# and X1-X14. One perl substitution each, restored after its run. A run counts
|
||||||
|
# as killed when any test fails or is cancelled, or the outer timeout fires.
|
||||||
|
# Usage: mutants.sh <tree> <outdir>
|
||||||
|
set -u
|
||||||
|
T="$1"; O="$2"; cd "$T"
|
||||||
|
run() {
|
||||||
|
local id="$1" file="$2" expr="$3"
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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)"
|
||||||
|
elif [ "${f:-?}" = 0 ] && [ "${c:-?}" = 0 ]; then echo "$id SURVIVED"
|
||||||
|
else echo "$id killed (fail ${f:-?}, cancelled ${c:-?})"; fi
|
||||||
|
mv "$file.orig" "$file"
|
||||||
|
}
|
||||||
|
NT=packages/cli/src/notifier.mjs
|
||||||
|
HS=packages/cli/src/host.mjs
|
||||||
|
# Rocko's set, verbatim.
|
||||||
|
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//'
|
||||||
|
# Filbert's set.
|
||||||
|
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" \? typeof r\.day !== "string" \|\| !DAY\.test\(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\.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 X9 $NT 's/ if \(UNWRITABLE\.includes\(e\.code\)\) throw new CliError\(`notify journal is not writable[^\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(/'
|
||||||
|
run X14 scripts/bus-service.sh 's/^[^\n]*mkdir -m 0700 -p[^\n]*\n//m'
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Row 45 probes (Filbert). Run: node --test probe.test.mjs, with TREE set.
|
||||||
|
import { test } from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { appendFileSync, 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 = () => 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 }); };
|
||||||
|
|
||||||
|
// F2 timing: a binding typo (every DM 403s), polled every POLL_MS.
|
||||||
|
test("F2-T1 time from first refusal to gave-up, polling every POLL_MS", async (t) => {
|
||||||
|
const s = rig(t, "2026-10-08T05:00:00Z", refuse403);
|
||||||
|
s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" });
|
||||||
|
const n = s.make();
|
||||||
|
const start = s.clock.t.getTime();
|
||||||
|
for (let i = 0; i < 2000 && !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("T1 sends at", s.sends.map((x) => `+${(Date.parse(x.at) - start) / 1000}s`).join(" "));
|
||||||
|
log("T1 gave-up at", `+${(Date.parse(gave.at) - start) / 1000}s`, "=", ((Date.parse(gave.at) - start) / 60000).toFixed(1), "min");
|
||||||
|
});
|
||||||
|
|
||||||
|
// The typo is fixed an hour after the first refusal: does the DM land?
|
||||||
|
test("F2-T2 binding fixed after 60 min: the DM never lands", async (t) => {
|
||||||
|
const t0 = Date.parse("2026-10-08T05:00:00Z");
|
||||||
|
const s = rig(t, "2026-10-08T05:00:00Z", (now) => (now.getTime() - t0 < 60 * 60_000 ? refuse403() : { messageId: "123456789012345678" }));
|
||||||
|
s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" });
|
||||||
|
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("T2 after 4 h: outcomes", s.journal().filter((r) => r.kind === "dm").map((r) => r.outcome).join(","));
|
||||||
|
log("T2 sends", s.sends.filter((x) => x.nonce.startsWith("dm")).length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restart after two refusals: the in-memory backoff is lost.
|
||||||
|
test("F2-T3 restart drops the backoff: next attempt is immediate", async (t) => {
|
||||||
|
const s = rig(t, "2026-10-08T05:00:00Z", refuse403);
|
||||||
|
s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" });
|
||||||
|
let n = s.make();
|
||||||
|
await n.tick();
|
||||||
|
s.clock.t = new Date(s.clock.t.getTime() + 31_000);
|
||||||
|
await n.tick();
|
||||||
|
const before = s.sends.length;
|
||||||
|
n = s.make();
|
||||||
|
await n.tick();
|
||||||
|
log("T3 sends before restart", before, "after one tick post-restart", s.sends.length, "(same clock second)");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unknown outcomes and 429 never give up.
|
||||||
|
test("F2-T4 500s for 6 h: no gave-up", async (t) => {
|
||||||
|
const s = rig(t, "2026-10-08T05:00:00Z", () => { throw new RestOutcome("unknown", "dm: unknown", { status: 500 }); });
|
||||||
|
s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" });
|
||||||
|
const n = s.make();
|
||||||
|
for (let i = 0; i < 6 * 120; i++) { await n.tick(); s.clock.t = new Date(s.clock.t.getTime() + POLL_MS); }
|
||||||
|
log("T4 dm sends", s.sends.filter((x) => x.nonce.startsWith("dm")).length, "gave-up", s.journal().some((r) => r.outcome === "gave-up"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// A digest 403 forever: the digest has no limit (only DMs give up).
|
||||||
|
test("F2-T5 digest refusals: retried, never gave-up", async (t) => {
|
||||||
|
const s = rig(t, "2026-10-08T13:00:00Z", refuse403);
|
||||||
|
const n = s.make();
|
||||||
|
for (let i = 0; i < 120; i++) { await n.tick(); s.clock.t = new Date(s.clock.t.getTime() + POLL_MS); }
|
||||||
|
log("T5 digest sends in 1 h", s.sends.length, "gave-up", s.journal().some((r) => r.outcome === "gave-up"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// J4 on append: a non-string messageId is written, and the next open refuses.
|
||||||
|
test("J4-A1 append does not type-check: a numeric messageId bricks the next open", async (t) => {
|
||||||
|
const s = rig(t, "2026-10-08T05:00:00Z", () => ({ messageId: 123 }));
|
||||||
|
s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" });
|
||||||
|
const r = await s.make().tick();
|
||||||
|
log("A1 tick", JSON.stringify(r), "line", JSON.stringify(s.journal()[0]));
|
||||||
|
try { s.make(); log("A1 reopen ok"); } catch (e) { log("A1 reopen", e.exitCode, e.message.replace(s.dataRoot, "<root>")); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// J4: a journal written by the base (row 39) code still opens.
|
||||||
|
test("J4-A2 a line the row 39 writer produced still opens", (t) => {
|
||||||
|
const s = rig(t, "2026-10-08T05:00:00Z", refuse403);
|
||||||
|
const file = journalPath(s.dataRoot, "demo");
|
||||||
|
openJournal(file);
|
||||||
|
const at = new Date("2026-10-08T05:00:00Z").toISOString();
|
||||||
|
appendFileSync(file, [
|
||||||
|
{ at, kind: "dm", decision: "d1", outcome: "confirmed", messageId: "1" },
|
||||||
|
{ at, kind: "dm", decision: "d2", outcome: "refused", messageId: null, status: 403 },
|
||||||
|
{ at, kind: "dm", decision: "d3", outcome: "unknown", messageId: null },
|
||||||
|
{ at, kind: "digest", decision: null, day: "2026-10-08", outcome: "confirmed", messageId: "2" },
|
||||||
|
{ at, kind: "digest", decision: null, day: "2026-10-09", outcome: "refused", messageId: null, status: 429 },
|
||||||
|
].map((r) => `${JSON.stringify(r)}\n`).join(""));
|
||||||
|
const j = openJournal(file);
|
||||||
|
log("A2 ok sent", [...j.sent].join(","), "days", [...j.days].join(","), "refusals", JSON.stringify([...j.refusals]));
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Filbert, row 45: the host.mjs:144/150 window. The broker is SIGKILLed and
|
||||||
|
// dead, but this process has not yet run the disconnect, so connected is
|
||||||
|
// still true. Does send() with no callback raise an unhandled 'error'?
|
||||||
|
// argv[2]: "nocb" | "cb"
|
||||||
|
import { fork, spawnSync } from "node:child_process";
|
||||||
|
import { once } from "node:events";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
if (process.argv[2] === "child") { process.send("up"); setInterval(() => {}, 1000); }
|
||||||
|
else {
|
||||||
|
const mode = process.argv[2];
|
||||||
|
const c = fork(fileURLToPath(import.meta.url), ["child"], { stdio: ["ignore", "inherit", "inherit", "ipc"] });
|
||||||
|
await once(c, "message");
|
||||||
|
process.on("exit", (code) => console.log(mode, "process exit code", code));
|
||||||
|
c.kill("SIGKILL");
|
||||||
|
spawnSync("sleep", ["0.3"]); // block the loop: the child is dead, the disconnect not yet seen
|
||||||
|
console.log(mode, "connected before send", c.connected);
|
||||||
|
if (c.connected) {
|
||||||
|
if (mode === "cb") c.send({ op: "close" }, (e) => console.log(mode, "callback got", e?.code ?? "no error"));
|
||||||
|
else c.send({ op: "close" });
|
||||||
|
}
|
||||||
|
process.exitCode = 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (24.241496ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (27.401376ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (27.377079ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (18.540838ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (15.718237ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (17.673868ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (26.350248ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (12.074309ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (17.714459ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (23.654992ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (12.601793ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (21.489231ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (12.121438ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (18.100019ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.663501ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (7.494723ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.614846ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.953331ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.997646ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.426004ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.639842ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (0.796532ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.31124ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.492702ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (30.750778ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (19.639241ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (25.365852ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (90.577896ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (57.911085ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (81.062244ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (93.951236ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (54.687669ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (37.187253ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (57.471264ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (85.698753ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (32.966804ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (23.112882ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (18.143623ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (30.935331ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (22.429967ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (23.526473ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (18.94584ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (20.00439ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (20.09949ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (20.004313ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (20.507454ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (24.840891ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (17.620181ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (16.372243ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (17.06366ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (61.104182ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (11.117121ms)
|
||||||
|
✔ async transactions refuse before invoking their function (8.582549ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (26.654451ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (10.565667ms)
|
||||||
|
✔ a bad entry refuses the whole record (10.346049ms)
|
||||||
|
✔ taskView reads the projection for one business (14.743855ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (23.223528ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (278.8713ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (14.399987ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (16.003453ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (104.50676ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (30.89905ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (25.599399ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (13.8714ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.484692ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (16.628757ms)
|
||||||
|
ℹ tests 67
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 67
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 620.262652
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (1.867049ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (6.613216ms)
|
||||||
|
✔ two instances may share a definition (1.553281ms)
|
||||||
|
✔ top-level refusals (4.394638ms)
|
||||||
|
✔ arbiters and projects (6.806464ms)
|
||||||
|
✔ role instances (4.007157ms)
|
||||||
|
✔ Vikunja bots (10.644466ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (3.567703ms)
|
||||||
|
✔ credential references match the definition's services (4.264474ms)
|
||||||
|
✔ launch (12.958131ms)
|
||||||
|
✔ loadBusiness: file checks (2.079948ms)
|
||||||
|
✔ loadBusiness: not a regular file (45.475837ms)
|
||||||
|
✔ loading writes nothing (1.248191ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (2.636431ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (0.938997ms)
|
||||||
|
✔ usage errors exit 4 (306.920279ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (75.941648ms)
|
||||||
|
✔ validate: project files (372.761359ms)
|
||||||
|
✔ validate: missing files and a broken system config (286.961712ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (92.078302ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (69.525005ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (212.064917ms)
|
||||||
|
✔ resolve: prints one instance's record (225.62473ms)
|
||||||
|
✔ resolve: refusals (438.939536ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (2.990356ms)
|
||||||
|
✔ check: a good file has no problems (0.88057ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.344506ms)
|
||||||
|
✔ check: file problems (1.133557ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.180256ms)
|
||||||
|
✔ check: dates and environment references (0.53199ms)
|
||||||
|
✔ path and load (2.600044ms)
|
||||||
|
✔ refusals (1.590197ms)
|
||||||
|
✔ systemVars flattens the validated config (2.334227ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (5.382138ms)
|
||||||
|
✔ limits narrow the definition and never widen it (2.738644ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (5.289806ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (2.998882ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (1.66732ms)
|
||||||
|
✔ classify (1.855641ms)
|
||||||
|
✔ the record carries what the broker and launcher need (1.587725ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (10.439567ms)
|
||||||
|
✔ refusals (3.815951ms)
|
||||||
|
✔ the four shipped version 2 roles load (3.112119ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.576915ms)
|
||||||
|
✔ shipped authority follows the note's table (0.5797ms)
|
||||||
|
✔ version 1 files keep loading with no authority (1.255039ms)
|
||||||
|
✔ the conductor policy isn't a role (0.287754ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.465952ms)
|
||||||
|
✔ version 2 refusals (1.508511ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (2.698625ms)
|
||||||
|
✔ credentials: Gitea scopes (0.935475ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.273969ms)
|
||||||
|
✔ credentials: services (0.668012ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (0.725598ms)
|
||||||
|
✔ every key names known layers and a merge rule (0.99936ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (0.880079ms)
|
||||||
|
✔ types (2.195618ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.322584ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.431596ms)
|
||||||
|
✔ merge doesn't change its inputs (0.188001ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2158.961409
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (18.683142ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (23.175532ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (12.54802ms)
|
||||||
|
✔ decide prints a declining choice as declining (12.654365ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (12.795995ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (10.791338ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (12.657588ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (10.224266ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (11.932399ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (10.850137ms)
|
||||||
|
✔ agents and tasks print through the broker (9.820295ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.459087ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (47.077328ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (41.209336ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (36.360521ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (37.183043ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (34.767644ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (65.444872ms)
|
||||||
|
✔ empty views say so (0.866ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (1.026474ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.169169ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (868.369289ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (172.365188ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (147.095184ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (31.864675ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (203.54711ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (257.814577ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (111.480249ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (729.54278ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (33.560404ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (25.853917ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (18.485699ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (12.527143ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.235022ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (13.460085ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.722078ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.203833ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (0.835125ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (13.241518ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.243073ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.159201ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (1.998131ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.793662ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.743756ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.392904ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (110.519055ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (34.393972ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2176.092888ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.551635ms)
|
||||||
|
ℹ tests 49
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 49
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2660.213546
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (6.010669ms)
|
||||||
|
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.427305ms)
|
||||||
|
✔ completed smoke replies and ordinary questions are idle, not human blockers (1.380473ms)
|
||||||
|
✔ only an explicit first-line input request makes a finished reply waiting (0.350763ms)
|
||||||
|
✔ tool activity, user text, errors and unfinished turns override attention text (0.244757ms)
|
||||||
|
✔ STOP access failure is unknown, not absence, under a non-root identity (46.560522ms)
|
||||||
|
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (4.628469ms)
|
||||||
|
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (2.347115ms)
|
||||||
|
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (8.011484ms)
|
||||||
|
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (1.083299ms)
|
||||||
|
✔ server rescans connector discovery and refuses HTTP reply without transport (44.367227ms)
|
||||||
|
✔ connector session links and linked directories are not read (1.464268ms)
|
||||||
|
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (3.99754ms)
|
||||||
|
✔ CLI print uses the relaunch notice instead of old current preview (95.092586ms)
|
||||||
|
✔ connector owner and fixed task never inherit a native relaunch notice (3.122909ms)
|
||||||
|
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.855835ms)
|
||||||
|
✔ loadConfig: missing file throws ConfigError (2.170209ms)
|
||||||
|
✔ loadConfig: invalid JSON throws ConfigError (0.404033ms)
|
||||||
|
✔ loadConfig: missing dataRoot throws ConfigError (0.309222ms)
|
||||||
|
✔ loadConfig: relative dataRoot throws ConfigError (0.423873ms)
|
||||||
|
✔ loadConfig: valid config returns dataRoot (0.828693ms)
|
||||||
|
✔ findNewestSession: picks the newest by mtime among two files (0.711595ms)
|
||||||
|
✔ findNewestSession: finds files in nested subdirectories (0.463025ms)
|
||||||
|
✔ findNewestSession: returns null for a missing dir (0.166929ms)
|
||||||
|
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.91581ms)
|
||||||
|
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.74382ms)
|
||||||
|
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.68941ms)
|
||||||
|
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.513219ms)
|
||||||
|
✔ deriveState: full state table (0.217441ms)
|
||||||
|
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.584263ms)
|
||||||
|
✔ rule: newest entry is a tool result with no assistant text after it is working (0.4952ms)
|
||||||
|
✔ rule: a finished ordinary turn is idle, even if it says your move (0.461533ms)
|
||||||
|
✔ task: the first user message of the session, from text blocks (0.358351ms)
|
||||||
|
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.40596ms)
|
||||||
|
✔ task: no user message in the log means null (shown as unknown), never a guess (0.36415ms)
|
||||||
|
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.529594ms)
|
||||||
|
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.638536ms)
|
||||||
|
✔ scan: the written record carries task, workspace and activeProject (0.93127ms)
|
||||||
|
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.9256ms)
|
||||||
|
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.674264ms)
|
||||||
|
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (1.455794ms)
|
||||||
|
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.591052ms)
|
||||||
|
✔ loadRegistrations: a missing seatsDir gives empty lists (0.216108ms)
|
||||||
|
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (1.142905ms)
|
||||||
|
✔ 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.43043ms)
|
||||||
|
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (1.014589ms)
|
||||||
|
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.775468ms)
|
||||||
|
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.638485ms)
|
||||||
|
✔ scanAgent: ageSeconds is computed from the injected now (0.325922ms)
|
||||||
|
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.245852ms)
|
||||||
|
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.434228ms)
|
||||||
|
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.552141ms)
|
||||||
|
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (1.187081ms)
|
||||||
|
✔ scan: relative boardDir throws ConfigError (0.145247ms)
|
||||||
|
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (86.948336ms)
|
||||||
|
✔ CLI: missing config exits 2 with a refused: message (60.852136ms)
|
||||||
|
✔ CLI: unknown command exits 2 (70.857596ms)
|
||||||
|
✔ CLI: unknown --liveness value exits 2 (77.483179ms)
|
||||||
|
✔ panesRunPi: true when any trimmed line equals 'pi' (0.263144ms)
|
||||||
|
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.09522ms)
|
||||||
|
✔ tmuxIsAlive: a pane running pi is alive (0.206673ms)
|
||||||
|
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.10368ms)
|
||||||
|
✔ tmuxIsAlive: no such tmux session is not alive (0.058347ms)
|
||||||
|
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.067732ms)
|
||||||
|
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.105159ms)
|
||||||
|
✔ parsePanes: one pane per line, command and optional tab-separated path (0.08465ms)
|
||||||
|
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.085903ms)
|
||||||
|
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.102332ms)
|
||||||
|
✔ loadSeen: missing file returns {} (0.17577ms)
|
||||||
|
✔ loadSeen: invalid JSON throws ConfigError (0.222621ms)
|
||||||
|
✔ loadSeen: a JSON array throws ConfigError (0.215859ms)
|
||||||
|
✔ loadSeen: a non-string value throws ConfigError (0.197642ms)
|
||||||
|
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.474763ms)
|
||||||
|
✔ markSeen: seen false deletes the key (0.295837ms)
|
||||||
|
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.214289ms)
|
||||||
|
✔ markSeen: project containing '/' throws ConfigError (0.663767ms)
|
||||||
|
✔ markSeen: non-boolean seen throws ConfigError (0.163287ms)
|
||||||
|
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.366334ms)
|
||||||
|
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.291198ms)
|
||||||
|
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.267725ms)
|
||||||
|
✔ scanAgent: an error-state session with a matching mark is seen (0.310516ms)
|
||||||
|
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.613896ms)
|
||||||
|
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.22487ms)
|
||||||
|
✔ 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.830851ms)
|
||||||
|
✔ 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.914619ms)
|
||||||
|
✔ 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.340662ms)
|
||||||
|
✔ isLoopbackHost: recognizes loopback hosts (1.861483ms)
|
||||||
|
✔ isLoopbackHost: rejects non-loopback hosts (6.326495ms)
|
||||||
|
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (4.294971ms)
|
||||||
|
✔ startServer: serves page, healthz, and a rescanning /api/board (52.193955ms)
|
||||||
|
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (10.198557ms)
|
||||||
|
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (3.653617ms)
|
||||||
|
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (67.059083ms)
|
||||||
|
✔ CLI: serve rejects a non-numeric --port with exit 2 (72.917415ms)
|
||||||
|
✔ CLI: scan still works after the async cli refactor (71.064375ms)
|
||||||
|
✔ CLI: live serve prints its URL and answers /healthz (74.988166ms)
|
||||||
|
✔ page.html: esc() escapes every HTML-significant character (0.657462ms)
|
||||||
|
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.813455ms)
|
||||||
|
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.279532ms)
|
||||||
|
✔ POST /api/seen with invalid JSON returns 400 (2.984055ms)
|
||||||
|
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.586391ms)
|
||||||
|
✔ POST /api/seen with a missing agent returns 400 (1.774095ms)
|
||||||
|
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.963635ms)
|
||||||
|
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (62.667316ms)
|
||||||
|
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.392194ms)
|
||||||
|
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.385316ms)
|
||||||
|
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.239229ms)
|
||||||
|
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.159999ms)
|
||||||
|
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.377441ms)
|
||||||
|
✔ 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.779315ms)
|
||||||
|
✔ 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 (39.730547ms)
|
||||||
|
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (36.773894ms)
|
||||||
|
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (34.067307ms)
|
||||||
|
✔ 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 (29.012725ms)
|
||||||
|
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (6.244474ms)
|
||||||
|
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.212342ms)
|
||||||
|
✔ 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.686283ms)
|
||||||
|
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.987338ms)
|
||||||
|
✔ 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.390807ms)
|
||||||
|
✔ 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 (9.577638ms)
|
||||||
|
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (36.075218ms)
|
||||||
|
✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (6.60021ms)
|
||||||
|
✔ every refusal code the reader can raise has an HTTP status (3.982216ms)
|
||||||
|
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (49.201664ms)
|
||||||
|
ℹ tests 124
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 124
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 827.412997
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
✔ W1: two processes acquire the same pair at once; exactly one claim (208.33252ms)
|
||||||
|
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (9.811798ms)
|
||||||
|
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (5.537445ms)
|
||||||
|
✔ W2: acquire while a claim is reserved or active refuses already-active (228.104072ms)
|
||||||
|
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (324.120499ms)
|
||||||
|
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (209.598509ms)
|
||||||
|
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (27.238547ms)
|
||||||
|
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (22.122492ms)
|
||||||
|
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (3.087845ms)
|
||||||
|
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5701.445297ms)
|
||||||
|
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (22244.010272ms)
|
||||||
|
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (149.81459ms)
|
||||||
|
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (88.345296ms)
|
||||||
|
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (223.831116ms)
|
||||||
|
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (188.323071ms)
|
||||||
|
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (217.683612ms)
|
||||||
|
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (32.560634ms)
|
||||||
|
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (96.660024ms)
|
||||||
|
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (32.019907ms)
|
||||||
|
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (91.969696ms)
|
||||||
|
✔ W11: the controller writes no session file; only the fake engine's own appends appear (22.329944ms)
|
||||||
|
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (54.201621ms)
|
||||||
|
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (60.552307ms)
|
||||||
|
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.596267ms)
|
||||||
|
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.126052ms)
|
||||||
|
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.770404ms)
|
||||||
|
✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2653.677511ms)
|
||||||
|
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (129.308388ms)
|
||||||
|
✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2165.26786ms)
|
||||||
|
✔ K4: two engines; force stop one; the other survives by independent observation (4287.963097ms)
|
||||||
|
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2182.565146ms)
|
||||||
|
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2258.603392ms)
|
||||||
|
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2153.528491ms)
|
||||||
|
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4403.942608ms)
|
||||||
|
✖ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (180.585304ms)
|
||||||
|
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (302.630477ms)
|
||||||
|
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (265.388551ms)
|
||||||
|
✔ K6: recover without proof, without confirmation, or with changed pins is refused (69.424274ms)
|
||||||
|
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (38.2806ms)
|
||||||
|
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (48.757019ms)
|
||||||
|
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3026.567752ms)
|
||||||
|
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (5.577601ms)
|
||||||
|
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (35.813713ms)
|
||||||
|
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (28.909283ms)
|
||||||
|
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (49.842944ms)
|
||||||
|
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (37.949131ms)
|
||||||
|
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (34.393597ms)
|
||||||
|
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (55.353224ms)
|
||||||
|
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (24.11355ms)
|
||||||
|
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.684512ms)
|
||||||
|
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (26.735815ms)
|
||||||
|
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (127.693023ms)
|
||||||
|
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (43.227988ms)
|
||||||
|
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (26.124057ms)
|
||||||
|
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (38.762027ms)
|
||||||
|
✔ 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 (38.724253ms)
|
||||||
|
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.71162ms)
|
||||||
|
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (30.187625ms)
|
||||||
|
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (48.049709ms)
|
||||||
|
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (43.974693ms)
|
||||||
|
✔ terminal: engine control characters are made visible; a lost connection refuses submit (30.554731ms)
|
||||||
|
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.71206ms)
|
||||||
|
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.501472ms)
|
||||||
|
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.71016ms)
|
||||||
|
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.205325ms)
|
||||||
|
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (440.560715ms)
|
||||||
|
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (70.048519ms)
|
||||||
|
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (89.612495ms)
|
||||||
|
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (151.740861ms)
|
||||||
|
✔ H4: self-takeover is refused (19.567355ms)
|
||||||
|
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (126.266639ms)
|
||||||
|
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (127.987954ms)
|
||||||
|
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (30.194643ms)
|
||||||
|
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (90.744705ms)
|
||||||
|
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (137.197933ms)
|
||||||
|
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (15.871752ms)
|
||||||
|
✔ H13: a retry with the same request ID and different text is refused (16.248157ms)
|
||||||
|
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (137.840526ms)
|
||||||
|
✔ H15: a revoked connection's command is refused; the revocation fence holds (67.750797ms)
|
||||||
|
✔ H16: a second controller for the same session refuses already-active; the first is untouched (18.408706ms)
|
||||||
|
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (58.879625ms)
|
||||||
|
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (54.75082ms)
|
||||||
|
✔ H18: two prompts before any native output: the second refuses busy; one engine write (15.210695ms)
|
||||||
|
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (119.912673ms)
|
||||||
|
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.74475ms)
|
||||||
|
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (526.777789ms)
|
||||||
|
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (406.518664ms)
|
||||||
|
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (372.815504ms)
|
||||||
|
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2470.446235ms)
|
||||||
|
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (457.248222ms)
|
||||||
|
✔ a plain conversation: catalogue row, one page, CHAT-01 records (11.82056ms)
|
||||||
|
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.271047ms)
|
||||||
|
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.730979ms)
|
||||||
|
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (3.52433ms)
|
||||||
|
✔ F1: an unreadable fork is never merged into another branch's history (1.850162ms)
|
||||||
|
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (4.636359ms)
|
||||||
|
✔ F1: a file whose entries are all unreadable shows a notice per line (1.641695ms)
|
||||||
|
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.301832ms)
|
||||||
|
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (7.597286ms)
|
||||||
|
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.634047ms)
|
||||||
|
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.869533ms)
|
||||||
|
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (11.185671ms)
|
||||||
|
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (14.562294ms)
|
||||||
|
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (15.568579ms)
|
||||||
|
✔ F8: a file swapped for a symlink after the catalogue is refused (3.87859ms)
|
||||||
|
✔ F9: registrations never add or redirect a root (2.922432ms)
|
||||||
|
✔ F10: a header cwd naming another project is refused (6.505993ms)
|
||||||
|
✔ F11: parentSession renders with a marker and the parent is never opened (1.280591ms)
|
||||||
|
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (8.701968ms)
|
||||||
|
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (3.047678ms)
|
||||||
|
✔ F12: a second root (Pi's resetLeaf) starts its own branch (2.529818ms)
|
||||||
|
✔ F13: compaction is a marker in place, then the retained content (1.200056ms)
|
||||||
|
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (927.08769ms)
|
||||||
|
✔ fragments never cut a surrogate pair and keep an empty string (6.246556ms)
|
||||||
|
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.891982ms)
|
||||||
|
✔ unknown conversations, empty files and non-Pi files refuse (3.354143ms)
|
||||||
|
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.403634ms)
|
||||||
|
✔ a seat directory without search permission refuses that root, not the catalogue (2.386158ms)
|
||||||
|
✔ every page and cursor is a valid CHAT-01 record (963.005061ms)
|
||||||
|
✔ the engine pin holds for the installed package (4.64981ms)
|
||||||
|
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (492.509878ms)
|
||||||
|
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (449.300806ms)
|
||||||
|
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (99.699963ms)
|
||||||
|
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (56.005591ms)
|
||||||
|
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (36.010734ms)
|
||||||
|
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (23.120368ms)
|
||||||
|
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (40.605984ms)
|
||||||
|
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (32.198961ms)
|
||||||
|
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (129.781085ms)
|
||||||
|
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (60.975823ms)
|
||||||
|
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1540.123798ms)
|
||||||
|
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (17.607252ms)
|
||||||
|
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (71.219891ms)
|
||||||
|
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (14.260687ms)
|
||||||
|
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (19.355928ms)
|
||||||
|
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (40.425771ms)
|
||||||
|
✔ N10: fake conformance (34.674337ms)
|
||||||
|
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (45.385869ms)
|
||||||
|
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (26.329147ms)
|
||||||
|
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (70.140045ms)
|
||||||
|
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (30.960887ms)
|
||||||
|
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (33.997394ms)
|
||||||
|
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (24.416409ms)
|
||||||
|
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (13.466921ms)
|
||||||
|
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (26.194657ms)
|
||||||
|
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (105.895233ms)
|
||||||
|
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (137.573966ms)
|
||||||
|
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (85.246925ms)
|
||||||
|
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (16.090188ms)
|
||||||
|
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (14.423581ms)
|
||||||
|
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (14.347618ms)
|
||||||
|
✔ 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 (44.072894ms)
|
||||||
|
ℹ tests 152
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 149
|
||||||
|
ℹ fail 3
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 32310.893453
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at packages/conversation/tests/cohort.test.mjs:139:1
|
||||||
|
✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2653.677511ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: the escaped child is a listed member
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r45/base/packages/conversation/tests/cohort.test.mjs:151:12)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||||
|
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||||
|
generatedMessage: false,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: false,
|
||||||
|
expected: true,
|
||||||
|
operator: '==',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at packages/conversation/tests/cohort.test.mjs:176:1
|
||||||
|
✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2165.26786ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: the member ignored TERM
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r45/base/packages/conversation/tests/cohort.test.mjs:190:12)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: false,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: false,
|
||||||
|
expected: true,
|
||||||
|
operator: '==',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at packages/conversation/tests/cohort.test.mjs:426:3
|
||||||
|
✖ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (180.585304ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: the member is alive across the crash
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r45/base/packages/conversation/tests/cohort.test.mjs:431:12)
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: false,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: false,
|
||||||
|
expected: true,
|
||||||
|
operator: '==',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.201101ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.660034ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.580701ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.486235ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (21.333687ms)
|
||||||
|
✔ 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.393055ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (9.050164ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.631665ms)
|
||||||
|
✔ authorize: open channel, listed user (1.952524ms)
|
||||||
|
✔ authorize: wrong guild (0.194831ms)
|
||||||
|
✔ authorize: no guild (DM) (0.147706ms)
|
||||||
|
✔ authorize: unlisted channel (0.285655ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.196753ms)
|
||||||
|
✔ authorize: thread of listed parent (0.435598ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.164288ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.135153ms)
|
||||||
|
✔ authorize: unlisted user (0.160797ms)
|
||||||
|
✔ authorize: no author (0.24555ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (1.405614ms)
|
||||||
|
✔ authorize: system author (0.126607ms)
|
||||||
|
✔ authorize: the bot itself (0.082559ms)
|
||||||
|
✔ authorize: webhook (0.078451ms)
|
||||||
|
✔ authorize: mention channel without mention (0.129292ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.130115ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.138676ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.08371ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.097637ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.089673ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.077673ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.128817ms)
|
||||||
|
✔ authorize: not an object (0.062853ms)
|
||||||
|
✔ authorize: no id (0.161612ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.070759ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.058304ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.476843ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.126935ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (4.063052ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.558439ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.781921ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.743709ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.132428ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.614055ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (2.040162ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.347954ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (84.997781ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.854618ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (312.162463ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (200.198135ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (116.785106ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.681435ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.164195ms)
|
||||||
|
✔ 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 (25.061237ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (23.476294ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.371221ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.19478ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.298255ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.87817ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.869668ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.761047ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.75218ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.78081ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (40.645485ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.614341ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (4.192219ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.866001ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.168526ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.138629ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.999622ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.428104ms)
|
||||||
|
✔ 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.076407ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.717986ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.409683ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.044094ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.595509ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.590484ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.907284ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.336321ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.24491ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.4446ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.222964ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.652689ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.596485ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (2.135157ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (60.544838ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (35.320946ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (29.839246ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (333.239021ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (229.917302ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (104.025224ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.179554ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (132.927031ms)
|
||||||
|
✔ 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.653029ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.160788ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.627204ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.520418ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.886242ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (27.00359ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.705536ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.638535ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.923849ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.632194ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.429861ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.788995ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (1.041148ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.397092ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (71.308392ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (37.288053ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (65.469921ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.257703ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (79.837685ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (85.969098ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (96.306132ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (199.121223ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (61.956937ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (86.647333ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (210.101951ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (749.416922ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.298493ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (5.429571ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.017847ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.535219ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (53.217434ms)
|
||||||
|
✔ 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 (292.904169ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.386939ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.427908ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.851219ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.514684ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (113.625484ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (65.633357ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.464313ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.44218ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.120122ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.201331ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (65.287043ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (43.180652ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (26.948772ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (64.561167ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (658.809302ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.434973ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.239627ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.599442ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.695726ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.531794ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.320663ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (2.450232ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.459636ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.677375ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (22.100773ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (8.043384ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.883186ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.004438ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.603316ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.745332ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.566197ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.527999ms)
|
||||||
|
✔ 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.51347ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.238868ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.210266ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.395749ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.409164ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.544056ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.663439ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.490327ms)
|
||||||
|
✔ tools: listing and search caps hold (9.712986ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.91641ms)
|
||||||
|
✔ 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.396493ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.118591ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.239234ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.472032ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.128317ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.204303ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.119455ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (3.260389ms)
|
||||||
|
✔ 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.63004ms)
|
||||||
|
✔ 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.242048ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.258013ms)
|
||||||
|
✔ 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.624952ms)
|
||||||
|
✔ 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.303252ms)
|
||||||
|
ℹ tests 178
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 178
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2634.058891
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (138.671771ms)
|
||||||
|
✔ the raw path accepts nothing else, and refuses before curl runs (423.976474ms)
|
||||||
|
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (306.653279ms)
|
||||||
|
✔ the raw path base URL has no override (68.146647ms)
|
||||||
|
✔ the JSON path is unchanged, and JSON never falls through to the raw path (268.204784ms)
|
||||||
|
✔ a file that changes between the two reads refuses before curl runs, with or without a body (796.716083ms)
|
||||||
|
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (747.472393ms)
|
||||||
|
✔ real helper GET HTTP 200 preserves exit 0 without credentials (18.039884ms)
|
||||||
|
✔ real helper POST HTTP 201 preserves exit 0 without credentials (12.248504ms)
|
||||||
|
✔ real helper GET HTTP 403 preserves exit 1 without credentials (11.836651ms)
|
||||||
|
✔ fixture git subjects only, follow-ups and three session kinds (119.607682ms)
|
||||||
|
✔ text and JSON carry same numbers, open and truncated title (186.370317ms)
|
||||||
|
✔ missing credentials exit 2, no-issues never calls API and shows unknown (169.209561ms)
|
||||||
|
✔ empty range gives no rows and zero totals (109.226221ms)
|
||||||
|
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (113.45241ms)
|
||||||
|
✔ close-only issue included, even median, missing metadata stays unknown (113.56368ms)
|
||||||
|
✔ unique commits but per-issue links count multiple tags once each (112.873951ms)
|
||||||
|
✔ page cap refuses rather than silently undercounting (101.322696ms)
|
||||||
|
✔ bad API payload not JSON refuses (96.714416ms)
|
||||||
|
✔ bad API payload {} refuses (94.745159ms)
|
||||||
|
✔ bad API payload [{"number":1}] refuses (89.245405ms)
|
||||||
|
✔ partial or malformed session log refuses with location, not content (101.876415ms)
|
||||||
|
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (132.282475ms)
|
||||||
|
✔ no sessions is an empty table; symlink source refuses (192.239786ms)
|
||||||
|
✔ reads only refactor even when another branch is checked out (105.435584ms)
|
||||||
|
✔ invalid dates, reverse dates and duplicate options refuse (70.488615ms)
|
||||||
|
✔ T3 agent assignments do not count as human in Table 2 (98.640321ms)
|
||||||
|
✔ preamble parsing and issue number boundaries (0.473611ms)
|
||||||
|
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.160889ms)
|
||||||
|
✔ no closed issues with human messages means undefined ratio, not invented zero (0.230338ms)
|
||||||
|
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (196.682254ms)
|
||||||
|
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (472.742418ms)
|
||||||
|
✔ T3: a HOME with no database exits 1 and names --no-t3 (88.149974ms)
|
||||||
|
✔ T3: a file that is not a database exits 1 and names --no-t3 (94.805533ms)
|
||||||
|
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (125.975358ms)
|
||||||
|
✔ T3: an unmapped thread addressed as a seat exits 1 (121.305514ms)
|
||||||
|
✔ T3: a header to another thread id is not cross-checked (123.101002ms)
|
||||||
|
✔ T3: no project, or two, for this root exits 1 (307.420146ms)
|
||||||
|
✔ T3: a removed column exits 1 and names it (103.087633ms)
|
||||||
|
✔ T3: a missing table exits 1 and names it (112.352744ms)
|
||||||
|
✔ T3: a counted row with an unknown role exits 1 without its text (134.640199ms)
|
||||||
|
✔ T3: a counted row with non-text content exits 1 without its text (134.30338ms)
|
||||||
|
✔ T3: a counted row with an unparseable created_at exits 1 without its text (124.285473ms)
|
||||||
|
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (228.991249ms)
|
||||||
|
✔ T3: a human message with no event counts in humanWithoutEvent (127.281084ms)
|
||||||
|
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (224.491946ms)
|
||||||
|
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (240.564015ms)
|
||||||
|
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (58.530531ms)
|
||||||
|
✔ T3: a symlink at ~/.t3 exits 1 (90.708515ms)
|
||||||
|
✔ T3: a symlink at ~/.t3/userdata exits 1 (85.405863ms)
|
||||||
|
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (87.869959ms)
|
||||||
|
✔ T3: with --t3-db, a symlinked file or directory exits 1 (172.781915ms)
|
||||||
|
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (134.769444ms)
|
||||||
|
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (123.840167ms)
|
||||||
|
✔ T3 WAL: -wal without -shm in a writable directory is read (141.073277ms)
|
||||||
|
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (134.623712ms)
|
||||||
|
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (116.480721ms)
|
||||||
|
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5123.515152ms)
|
||||||
|
✔ a done row whose closing issue is open is a violation; a row that is not done is not (2.262245ms)
|
||||||
|
✔ an issue several rows close is expected closed only once all of them are done (0.636824ms)
|
||||||
|
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.465932ms)
|
||||||
|
✔ each owner of an in-progress or in-review row gets one liveness class (8.945669ms)
|
||||||
|
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.42539ms)
|
||||||
|
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.251321ms)
|
||||||
|
✔ the text section always ends in a count and a result, and never prints a full pass (0.389998ms)
|
||||||
|
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (30.271711ms)
|
||||||
|
✔ issue states: open list first, then the metric page, then at most 10 lookups (318.077146ms)
|
||||||
|
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (419.682997ms)
|
||||||
|
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (186.450359ms)
|
||||||
|
✔ a helper call past the deadline is killed with its child, and the call reports it (2007.63492ms)
|
||||||
|
✔ readQueue loads queue.json through the queue validator and refuses anything else (83.063941ms)
|
||||||
|
✔ protected changes list every in-range entry that changes a required or parked row (260.831405ms)
|
||||||
|
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (522.539179ms)
|
||||||
|
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (222.158594ms)
|
||||||
|
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (282.600961ms)
|
||||||
|
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (253.421151ms)
|
||||||
|
✔ --unsupported-runtime repeats once per seat and takes a seat name (327.815403ms)
|
||||||
|
✔ an unreadable config makes every owner invalid instead of passing them (132.587734ms)
|
||||||
|
ℹ tests 78
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 78
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 11297.728644
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
✔ pure resolution selects current default or explicit enrolled account (2.079609ms)
|
||||||
|
✔ scope is explicit, bounded and never inferred (2.230143ms)
|
||||||
|
✔ fork pin is preserved against default change, override, missing account and revocation (0.849241ms)
|
||||||
|
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.911311ms)
|
||||||
|
✔ only explicit synthetic credential forms and internal fixture stores admitted (6.641375ms)
|
||||||
|
✔ two concurrent workspaces of the same agent publish distinct complete private generations (61.909614ms)
|
||||||
|
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (34.701654ms)
|
||||||
|
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (47.614211ms)
|
||||||
|
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (61.009733ms)
|
||||||
|
✔ credential lock contention refuses without duplicate side effects (12.128114ms)
|
||||||
|
✔ symlinked pre-existing final target is refused and never followed (27.71374ms)
|
||||||
|
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.296627ms)
|
||||||
|
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (32.444858ms)
|
||||||
|
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (88.493926ms)
|
||||||
|
✔ refresh failure retains prior generation and store state (70.906183ms)
|
||||||
|
✔ refresh timeout retains prior generation and store state (150.598599ms)
|
||||||
|
✔ refresh malformed retains prior generation and store state (67.369629ms)
|
||||||
|
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (217.751194ms)
|
||||||
|
✔ invalid refresh options refuse before burning claim (36.896252ms)
|
||||||
|
✔ fixed fake process rotates both OAuth fields without mutating caller input (35.708505ms)
|
||||||
|
✔ concurrent isolated processes preserve separate provider credentials (28.15465ms)
|
||||||
|
✔ fake failure is refused with fixed diagnostics (25.606812ms)
|
||||||
|
✔ fake malformed is refused with fixed diagnostics (24.03472ms)
|
||||||
|
✔ fake timeout is refused with fixed diagnostics (104.074695ms)
|
||||||
|
✔ fake unchanged is refused with fixed diagnostics (30.105534ms)
|
||||||
|
✔ caller executable/environment injection is rejected before spawning (0.380258ms)
|
||||||
|
✔ valid fixture tree validates and lists without secrets (78.529984ms)
|
||||||
|
✔ unknown-field refuses (0.383423ms)
|
||||||
|
✔ invalid-id refuses uppercase and traversal shapes (0.285878ms)
|
||||||
|
✔ plain-http baseUrl requires allowInsecureTransport (0.304645ms)
|
||||||
|
✔ native provider rejects allowInsecureTransport (0.134402ms)
|
||||||
|
✔ unsupported credential type and kind refuse (0.155521ms)
|
||||||
|
✔ account provider-path mismatch refuses (0.1092ms)
|
||||||
|
✔ profile account refs must be provider/account shaped (0.247616ms)
|
||||||
|
✔ seat selection accepts fork pin field, validates account refs (0.252071ms)
|
||||||
|
✔ harness manifest id must equal executable (gate 1) (0.217346ms)
|
||||||
|
✔ CLI validate: duplicate provider id across files refuses (30.167855ms)
|
||||||
|
✔ CLI validate: missing referenced provider/account refuse (34.544812ms)
|
||||||
|
✔ CLI validate: broken JSON refuses without secret echo (29.453698ms)
|
||||||
|
✔ CLI usage errors exit 2 (52.764154ms)
|
||||||
|
✔ credential.json sibling presence does not break validation and is never read (74.009646ms)
|
||||||
|
✔ D1 missing, empty and structurally empty roots refuse, no list projection (195.778149ms)
|
||||||
|
✔ D1 required directory auth cannot be absent (64.656927ms)
|
||||||
|
✔ D1 required directory auth/providers cannot be absent (67.184602ms)
|
||||||
|
✔ D1 required directory auth/accounts cannot be absent (65.462742ms)
|
||||||
|
✔ D1 required directory auth/settings cannot be absent (61.524711ms)
|
||||||
|
✔ D1 required directory harnesses cannot be absent (64.933926ms)
|
||||||
|
✔ D1 root file and unreadable metadata refuse (126.575629ms)
|
||||||
|
✔ D2 no symlink traversal at auth/providers/openai-codex.json (61.71521ms)
|
||||||
|
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (67.898272ms)
|
||||||
|
✔ D2 no symlink traversal at auth/providers (52.901333ms)
|
||||||
|
✔ D2 no symlink traversal at auth (57.489447ms)
|
||||||
|
✔ D2 root and ancestor symlinks and lexical traversal refuse (186.814266ms)
|
||||||
|
✔ private filesystem modes enforced for root (62.871372ms)
|
||||||
|
✔ private filesystem modes enforced for auth (55.863789ms)
|
||||||
|
✔ private filesystem modes enforced for auth/providers/openai-codex.json (56.804104ms)
|
||||||
|
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (63.768965ms)
|
||||||
|
✔ D3 numeric version 1 only across all record kinds (1.309762ms)
|
||||||
|
✔ D4 nested unknown keys and missing per-kind required fields refuse (65.515115ms)
|
||||||
|
✔ D5 unenrolled default refuses even when account exists (64.050411ms)
|
||||||
|
✔ D6 provider/account credential type must match (64.344785ms)
|
||||||
|
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.459451ms)
|
||||||
|
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (165.076039ms)
|
||||||
|
✔ D9 malformed JSON diagnostics contain no content excerpt (64.218719ms)
|
||||||
|
✔ D10 missing metadata is missing-path, not invalid-json (62.667729ms)
|
||||||
|
✔ D10 library returns no partial entries on any invalid record (72.329981ms)
|
||||||
|
✔ null/scalar/array metadata refuses without stack or echo (254.035605ms)
|
||||||
|
✔ credential sibling is never opened, even when an unreadable symlink (35.816416ms)
|
||||||
|
✔ oversized metadata refuses before parsing (59.942545ms)
|
||||||
|
ℹ tests 69
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 69
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2279.776148
|
||||||
@@ -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 (1293.447642ms)
|
||||||
|
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1101.083362ms)
|
||||||
|
ℹ 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 (1074.456052ms)
|
||||||
|
ℹ 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 (1122.393406ms)
|
||||||
|
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1503.485391ms)
|
||||||
|
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1079.455381ms)
|
||||||
|
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1569.18684ms)
|
||||||
|
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1158.207634ms)
|
||||||
|
✔ F1: a shared-index change during the procedure is not committed (1264.74922ms)
|
||||||
|
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1315.032514ms)
|
||||||
|
✔ F1: a missing or a different hook refuses (834.923829ms)
|
||||||
|
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1402.70655ms)
|
||||||
|
✔ F1: the canary refuses a hook that git would not run (785.295278ms)
|
||||||
|
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1293.939835ms)
|
||||||
|
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (1068.985862ms)
|
||||||
|
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (1328.258272ms)
|
||||||
|
✔ bootstrap: the archived tests and validator run outside any repository (1203.511963ms)
|
||||||
|
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (1164.216663ms)
|
||||||
|
✔ general: a queue write after the snapshot is not committed (1266.8914ms)
|
||||||
|
✔ general: a snapshot whose log does not extend the base refuses (975.109162ms)
|
||||||
|
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (207.935884ms)
|
||||||
|
✔ general: environment overrides, a linked worktree and usage (612.446006ms)
|
||||||
|
✔ general: a queue path staged before the run refuses at step 1 (711.225406ms)
|
||||||
|
✔ general: HEAD's queue tests failing in the archive refuse (959.330303ms)
|
||||||
|
✔ genesis document serializes deterministically and replays (4.317024ms)
|
||||||
|
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.796549ms)
|
||||||
|
✔ a tampered result, receipt or viewSha fails replay (2.483399ms)
|
||||||
|
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.194516ms)
|
||||||
|
✔ add: defaults for an ordinary seat, privileged extras, refusals (4.962542ms)
|
||||||
|
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (6.010632ms)
|
||||||
|
✔ matrix: release, review round, changes requested and waiting-on-jason (12.885071ms)
|
||||||
|
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (8.050185ms)
|
||||||
|
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (19.180382ms)
|
||||||
|
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.426903ms)
|
||||||
|
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1537.958744ms)
|
||||||
|
✔ matrix: blocked keeps the claim and returns only to previousState (4.423046ms)
|
||||||
|
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.809439ms)
|
||||||
|
✔ field edits: who may change what (5.358298ms)
|
||||||
|
✔ set issues keeps a logged narrowing of closes (N10) (2.937073ms)
|
||||||
|
✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.297068ms)
|
||||||
|
✔ every accepted text renders to nine cells on every row (N8) (25.303514ms)
|
||||||
|
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.775011ms)
|
||||||
|
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.766389ms)
|
||||||
|
✔ replay holds every op id to the caller's rule (N11) (5.241836ms)
|
||||||
|
✔ note: owner, listed reviewer or privileged; empty clears (1.262486ms)
|
||||||
|
✔ assign moves the claim with the owner; done clears it (2.793087ms)
|
||||||
|
✔ render is byte-stable and escapes pipes (0.549068ms)
|
||||||
|
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.642409ms)
|
||||||
|
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (27.744196ms)
|
||||||
|
✔ canonical args make a retry's identity independent of list order (0.414978ms)
|
||||||
|
✔ manifests, headings and blob ids (0.632552ms)
|
||||||
|
✔ the migration map: one queue-map block, exact keys (0.804261ms)
|
||||||
|
✔ every call but `queue` reaches the seat CLI exactly as before A2 (731.541362ms)
|
||||||
|
✔ `queue` reaches the queue CLI with the rest of the arguments (238.216191ms)
|
||||||
|
✔ the pre-A2 fixture is the script A2 changed (0.287818ms)
|
||||||
|
✔ acquire publishes the record by link; release removes only its own lock (5.720988ms)
|
||||||
|
✔ a kill between the temp write and the link leaves no lock (45.276098ms)
|
||||||
|
✔ a short or failed temp write refuses and leaves no lock and no temp (2.721826ms)
|
||||||
|
✔ a link error other than EEXIST refuses (1.484864ms)
|
||||||
|
✔ an error after the link releases the lock: unreadable gate, failing temp stat (3.390013ms)
|
||||||
|
✔ a release that fails on a gate path is reported, never a stack trace (P1) (4.685306ms)
|
||||||
|
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10092.593192ms)
|
||||||
|
✔ two concurrent unlockers: the second refuses on the gate (31.012372ms)
|
||||||
|
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (2.190358ms)
|
||||||
|
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (2.065014ms)
|
||||||
|
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (2.789643ms)
|
||||||
|
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (5.874995ms)
|
||||||
|
✔ the same pid and start on a different boot is mismatch (1.057776ms)
|
||||||
|
✔ a foreign host is unknown whatever the local pid says; unlock refuses (92.308672ms)
|
||||||
|
✔ unreadable /proc: classification is unknown and acquire refuses (1.295867ms)
|
||||||
|
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.354373ms)
|
||||||
|
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (6.94049ms)
|
||||||
|
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (3.76974ms)
|
||||||
|
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (1.280141ms)
|
||||||
|
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (4.753956ms)
|
||||||
|
✔ the migration map validates and renders the golden genesis table (3.904784ms)
|
||||||
|
✔ the marked QUEUE.md holds every row and parked item between its markers (1.388114ms)
|
||||||
|
✔ map-check reports each kind of drift (5.03177ms)
|
||||||
|
✔ a request posts once as the requester; a retry sends nothing (644.508495ms)
|
||||||
|
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (3439.609121ms)
|
||||||
|
✔ the pre-send checks: GET user must name the requester, under the deadline (1220.867052ms)
|
||||||
|
✔ the lead's request refuses a token for login sage (723.708575ms)
|
||||||
|
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (493.172025ms)
|
||||||
|
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1765.486675ms)
|
||||||
|
✔ a same-op retry after a kill sends nothing, even with a stale view (2829.629069ms)
|
||||||
|
✔ a held lock at the outcome exits 3 and names what the transport said (628.916615ms)
|
||||||
|
✔ late outcomes: after an abandon, and after a resolve with the same or another id (1916.002633ms)
|
||||||
|
✔ resolve checks the comment: issue, markers, round, candidate and author (1924.486952ms)
|
||||||
|
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (727.313694ms)
|
||||||
|
✔ validateRow checks a request round's shape, which every replayed entry must keep (507.664198ms)
|
||||||
|
✔ request, changes, a new candidate, approval: every round pinned; no review files (1729.129969ms)
|
||||||
|
✔ a row with no reviewers opens a round that posts nothing (1395.516036ms)
|
||||||
|
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1543.262043ms)
|
||||||
|
✔ semantics: v1 entries replay as before; review entries need v2 (363.36264ms)
|
||||||
|
✔ set reviewers refuses the row's owner (2026-09-28) (269.605903ms)
|
||||||
|
✔ the owner records no verdict, even as a listed reviewer (369.885082ms)
|
||||||
|
✔ a request comment over the length limit is not sent (375.006737ms)
|
||||||
|
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (407.509135ms)
|
||||||
|
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1969.588199ms)
|
||||||
|
✔ genesis: refusals before anything is written (506.562641ms)
|
||||||
|
✔ genesis: the map must be committed, well formed, with committed briefs and seats (654.406808ms)
|
||||||
|
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (550.263442ms)
|
||||||
|
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (326.533418ms)
|
||||||
|
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (716.581482ms)
|
||||||
|
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (596.068448ms)
|
||||||
|
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (655.337554ms)
|
||||||
|
✔ a retried add returns the id it first allocated, after reassignment and after done (725.218464ms)
|
||||||
|
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (693.687288ms)
|
||||||
|
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1056.228626ms)
|
||||||
|
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (642.166551ms)
|
||||||
|
✔ add, set reviewers and assign refuse the row's owner as a reviewer (486.174853ms)
|
||||||
|
✔ the working-brief check: a changed working copy refuses the start and flags next (644.915802ms)
|
||||||
|
✔ next: resume first, then nothing for an idle seat; needs a seat (346.860331ms)
|
||||||
|
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (705.897481ms)
|
||||||
|
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1322.822023ms)
|
||||||
|
✔ a hand edit to queue.json refuses every verb, reads included (655.13831ms)
|
||||||
|
✔ verify and render --check leave bytes and mtimes unchanged (503.99518ms)
|
||||||
|
✔ render is byte-stable across runs and repositories (364.651361ms)
|
||||||
|
✔ snapshot and verify --snapshot (843.406358ms)
|
||||||
|
✔ usage errors exit 4 (649.156839ms)
|
||||||
|
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (178.455612ms)
|
||||||
|
✔ a rename failure: nothing visible, temp removed (149.66643ms)
|
||||||
|
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (191.396309ms)
|
||||||
|
✔ a directory fsync failure, then sync names the op (303.146379ms)
|
||||||
|
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (163.931607ms)
|
||||||
|
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (130.884674ms)
|
||||||
|
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (162.482988ms)
|
||||||
|
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (133.645043ms)
|
||||||
|
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (140.043364ms)
|
||||||
|
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (154.777597ms)
|
||||||
|
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (124.738055ms)
|
||||||
|
✔ a view write that fails keeps the op and reports a stale view (125.171976ms)
|
||||||
|
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (586.757792ms)
|
||||||
|
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (651.543058ms)
|
||||||
|
✔ SIGKILL after the witness, before the view: the stale refusal names the op (597.367324ms)
|
||||||
|
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (585.535612ms)
|
||||||
|
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (173.622174ms)
|
||||||
|
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1210.247576ms)
|
||||||
|
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (221.84185ms)
|
||||||
|
✔ a header edit during a write: the op stands, the view write is skipped with a warning (153.28568ms)
|
||||||
|
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (132.389949ms)
|
||||||
|
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (130.677054ms)
|
||||||
|
✔ a writer paused before and after the witness rename: readers see a tail, then a match (147.365771ms)
|
||||||
|
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (498.356751ms)
|
||||||
|
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (469.221844ms)
|
||||||
|
✔ the platform check refuses other filesystems (130.864218ms)
|
||||||
|
✔ tmpfs passes only a test layer that allows it (N5) (158.150412ms)
|
||||||
|
✔ unlock keeps a multi-line lock record on stdout (P3) (209.279676ms)
|
||||||
|
ℹ tests 148
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 148
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 26358.293639
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
✔ resolveSeat: by name under --repo resolves the repo layout (1.481206ms)
|
||||||
|
✔ resolveSeat: by path resolves the fleet layout (0.362472ms)
|
||||||
|
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.856131ms)
|
||||||
|
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.828242ms)
|
||||||
|
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.904445ms)
|
||||||
|
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.554862ms)
|
||||||
|
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.289573ms)
|
||||||
|
✔ CLI launch: registers, execs the fake launch script, and passes args through (32.787236ms)
|
||||||
|
✔ CLI launch: --harness lands in the record (32.828675ms)
|
||||||
|
✔ CLI launch: the launch script's own exit code passes through (28.31954ms)
|
||||||
|
✔ CLI launch: relaunching a seat rewrites the one registration record (64.395537ms)
|
||||||
|
✔ CLI launch: omitting --task records an empty string, not null (29.227222ms)
|
||||||
|
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (81.319556ms)
|
||||||
|
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (127.788375ms)
|
||||||
|
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.395671ms)
|
||||||
|
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.553605ms)
|
||||||
|
✔ 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.726541ms)
|
||||||
|
✔ 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 (10.854969ms)
|
||||||
|
✔ 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 (379.051082ms)
|
||||||
|
ℹ tests 19
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 19
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 867.37898
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
✔ the boot config is checked before anything starts (25.484517ms)
|
||||||
|
✔ a business with no tracker entry refuses task verbs (18.675299ms)
|
||||||
|
✔ credential.expiring and .expired are recorded once per instance (44.712914ms)
|
||||||
|
✔ a token file that changes on disk records credential.changed (21.046939ms)
|
||||||
|
✔ autostart polls, reconciles and retries a startup the tracker was down for (41.67541ms)
|
||||||
|
✔ a refusal a restart must clear is not retried by the poll (12.489203ms)
|
||||||
|
✔ a poll that fires while two are queued is dropped (17.657755ms)
|
||||||
|
✔ close waits for a running verb and refuses one that has not started (45.913148ms)
|
||||||
|
✔ the bundled Vikunja is the pinned upstream image the runbook names (1.509112ms)
|
||||||
|
✔ every published port is on 127.0.0.1, and no secret is in the file (0.505503ms)
|
||||||
|
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (136.056128ms)
|
||||||
|
✔ the recorded task bodies pass the checks S3 applies to every read (0.687076ms)
|
||||||
|
✔ the client works against the fake over real HTTP with the platform fetch (111.070926ms)
|
||||||
|
✔ a correct install starts, and the first reconcile records tasks that already exist (39.319619ms)
|
||||||
|
✔ verbs refuse while a business is starting and after startup refused it (22.51381ms)
|
||||||
|
✔ startup refuses a token that can do more than its role needs (52.113844ms)
|
||||||
|
✔ startup refuses an unsupported version and flags an untested one (29.381676ms)
|
||||||
|
✔ startup refuses a board that the runbook did not install (42.307367ms)
|
||||||
|
✔ startup refuses a project the sync bot cannot read (7.346948ms)
|
||||||
|
✔ startup refuses a configured label the pm bot cannot see (9.464093ms)
|
||||||
|
✔ startup refuses an expired credential and a missing sync credential (18.02678ms)
|
||||||
|
✔ an unreachable tracker refuses with tracker-unavailable (8.588022ms)
|
||||||
|
✔ an edit in the UI is recorded once, with the fields that changed (98.406805ms)
|
||||||
|
✔ a move between open buckets is seen on the board, though updated does not change (45.2568ms)
|
||||||
|
✔ a person's comment is counted and a bot's is not (78.116956ms)
|
||||||
|
✔ the hourly reconcile catches a comment through comment_count (81.889781ms)
|
||||||
|
✔ a task closed in the UI leaves the open view with its done bucket (129.610061ms)
|
||||||
|
✔ a task that leaves the board is recorded as deleted, moved or out of reach (82.047323ms)
|
||||||
|
✔ a poll that read before a verb wrote does not overwrite the verb (41.528925ms)
|
||||||
|
✔ a tracker fault during a tick is reported and the next tick catches up (37.749804ms)
|
||||||
|
✔ a malformed answer refuses the tick with tracker-shape (15.198025ms)
|
||||||
|
✔ no token value reaches the database, the log or a refusal (47.096688ms)
|
||||||
|
✔ the first look at a task counts only comments inside the window (36.09698ms)
|
||||||
|
✔ task.create needs a recorded human request and a requirement id (60.738266ms)
|
||||||
|
✔ only labels named in the business file can be written (36.345449ms)
|
||||||
|
✔ task.schedule sets and clears a due date and relations (43.647102ms)
|
||||||
|
✔ assign and reassign move the role bots and record task.assigned (62.506333ms)
|
||||||
|
✔ task.update.assigned is for the assignee and records task.state (54.68988ms)
|
||||||
|
✔ a wrong expected digest records task.conflict and writes nothing (20.478626ms)
|
||||||
|
✔ a cross-role verb needs a resolved decision, used once (32.256943ms)
|
||||||
|
✔ task.close needs a verdict; after it every verb refuses with task-done (33.220223ms)
|
||||||
|
✔ a lost answer is settled by a re-read and never retried (38.272731ms)
|
||||||
|
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (44.567161ms)
|
||||||
|
✔ a task the sync bot cannot read refuses and records nothing (27.215388ms)
|
||||||
|
✔ verbs and polls for one business run one at a time (99.301469ms)
|
||||||
|
✔ a due date with milliseconds is written to the second (68.906673ms)
|
||||||
|
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (38.970276ms)
|
||||||
|
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (14.496191ms)
|
||||||
|
✔ a create whose final read fails succeeds and records task.created (34.358337ms)
|
||||||
|
✔ an edit between the last write and the final read shows as external on the next poll (39.988391ms)
|
||||||
|
✔ task.created is recorded when a later label write fails (16.768111ms)
|
||||||
|
ℹ tests 51
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 51
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 943.604698
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3375.0441ms)
|
||||||
|
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||||
|
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3792.760217ms)
|
||||||
|
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3283.267961ms)
|
||||||
|
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1639.235309ms)
|
||||||
|
✔ conversation view: a newer session with no readable history keeps the marker (919.340475ms)
|
||||||
|
✔ conversation view: seats without history say so and offer no reply (542.697818ms)
|
||||||
|
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2826.548118ms)
|
||||||
|
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54743.791523ms)
|
||||||
|
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2827.586217ms)
|
||||||
|
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22597.160796ms)
|
||||||
|
✔ loopback host and board origin fail closed (7.216403ms)
|
||||||
|
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (94.784523ms)
|
||||||
|
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (66.672652ms)
|
||||||
|
✔ unreachable board reports URL; CLI rejects unsupported options (597.716602ms)
|
||||||
|
ℹ tests 14
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 14
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 55083.850563
|
||||||
@@ -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 '521597bbe0cf51a0a1aa3b7a16f13fc7b59c98c6'.
|
||||||
|
|
||||||
|
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 '521597bbe0cf51a0a1aa3b7a16f13fc7b59c98c6'.
|
||||||
|
|
||||||
|
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: 38232 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/r45/base) 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,75 @@
|
|||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (22.380445ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (26.652343ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (26.817512ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (16.912593ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (17.484361ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (14.390004ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (22.844639ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (8.901475ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (14.697023ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (18.867203ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (16.611621ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (17.131669ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (15.160447ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (21.001418ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.412021ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.936616ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.671535ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.864599ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.892674ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.544106ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.134541ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (1.32011ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (1.011074ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.390425ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (28.53858ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (18.196485ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (25.135814ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (79.108955ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (48.829337ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (60.908712ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (107.873418ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (56.981582ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (42.470637ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (60.65868ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (80.57291ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (28.953415ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (21.466123ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (17.097423ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (31.814084ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (20.59826ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (18.052394ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (15.486208ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (18.124738ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (13.419772ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (20.491545ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (18.817052ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (25.541538ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (18.268778ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (17.236638ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (15.924833ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (58.344101ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (9.566566ms)
|
||||||
|
✔ async transactions refuse before invoking their function (7.56334ms)
|
||||||
|
✔ recordTask keeps sync reads and a role write apart (23.135531ms)
|
||||||
|
✔ read_at must be one canonical UTC format, so the projection compares strings safely (10.143766ms)
|
||||||
|
✔ a bad entry refuses the whole record (10.400234ms)
|
||||||
|
✔ taskView reads the projection for one business (14.293611ms)
|
||||||
|
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (24.359955ms)
|
||||||
|
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (275.793239ms)
|
||||||
|
✔ without an adapter the server refuses every task verb (16.577329ms)
|
||||||
|
✔ the runtime refuses an invalid adapter and closes a valid one (17.478752ms)
|
||||||
|
✔ the process loads the S3 adapter from plain-data trackers (115.152403ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (28.115562ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (19.883232ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (12.093017ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.273179ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (18.390612ms)
|
||||||
|
ℹ tests 67
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 67
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 596.109045
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (2.091087ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (5.321521ms)
|
||||||
|
✔ two instances may share a definition (2.037789ms)
|
||||||
|
✔ top-level refusals (6.351726ms)
|
||||||
|
✔ arbiters and projects (8.005976ms)
|
||||||
|
✔ role instances (3.893541ms)
|
||||||
|
✔ Vikunja bots (8.360818ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (3.148697ms)
|
||||||
|
✔ credential references match the definition's services (3.774244ms)
|
||||||
|
✔ launch (9.004305ms)
|
||||||
|
✔ loadBusiness: file checks (1.763435ms)
|
||||||
|
✔ loadBusiness: not a regular file (39.742587ms)
|
||||||
|
✔ loading writes nothing (1.395759ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (2.537876ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (0.542455ms)
|
||||||
|
✔ usage errors exit 4 (305.032324ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (67.820644ms)
|
||||||
|
✔ validate: project files (326.103036ms)
|
||||||
|
✔ validate: missing files and a broken system config (241.613221ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (65.283695ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (65.265828ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (200.706934ms)
|
||||||
|
✔ resolve: prints one instance's record (200.999245ms)
|
||||||
|
✔ resolve: refusals (402.197715ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (3.793344ms)
|
||||||
|
✔ check: a good file has no problems (1.913368ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.714324ms)
|
||||||
|
✔ check: file problems (1.363191ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.906015ms)
|
||||||
|
✔ check: dates and environment references (0.655015ms)
|
||||||
|
✔ path and load (3.164846ms)
|
||||||
|
✔ refusals (1.988671ms)
|
||||||
|
✔ systemVars flattens the validated config (2.92044ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (7.214308ms)
|
||||||
|
✔ limits narrow the definition and never widen it (3.430914ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (6.183548ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (1.860412ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (1.009894ms)
|
||||||
|
✔ classify (1.115549ms)
|
||||||
|
✔ the record carries what the broker and launcher need (0.857858ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (6.307397ms)
|
||||||
|
✔ refusals (2.303527ms)
|
||||||
|
✔ the four shipped version 2 roles load (3.823656ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (2.018995ms)
|
||||||
|
✔ shipped authority follows the note's table (1.408237ms)
|
||||||
|
✔ version 1 files keep loading with no authority (1.257812ms)
|
||||||
|
✔ the conductor policy isn't a role (0.384142ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.568308ms)
|
||||||
|
✔ version 2 refusals (1.861931ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (3.158576ms)
|
||||||
|
✔ credentials: Gitea scopes (1.219846ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.561772ms)
|
||||||
|
✔ credentials: services (0.559046ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (0.63259ms)
|
||||||
|
✔ every key names known layers and a merge rule (1.216219ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (1.149061ms)
|
||||||
|
✔ types (2.600103ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.416272ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.478389ms)
|
||||||
|
✔ merge doesn't change its inputs (0.197768ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 1954.930287
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (20.134056ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (26.241572ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (15.711639ms)
|
||||||
|
✔ decide prints a declining choice as declining (13.380758ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (9.236045ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (9.554273ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (10.621801ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (11.366682ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (12.082916ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (11.128705ms)
|
||||||
|
✔ agents and tasks print through the broker (10.449874ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.909287ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (48.708686ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (39.925021ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (35.254166ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (33.529767ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (41.164155ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (68.437879ms)
|
||||||
|
✔ empty views say so (0.935675ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (0.990128ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.159053ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (881.972396ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (135.708239ms)
|
||||||
|
✔ a second host for the same data root refuses with exit 3 while the first runs (73.349518ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.893626ms)
|
||||||
|
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (134.813304ms)
|
||||||
|
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (87.76665ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (36.929762ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (203.219855ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (276.768265ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (96.336866ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (651.277703ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (41.913796ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (25.237436ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (19.12349ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (15.04801ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.284039ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (15.246058ms)
|
||||||
|
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (18.300879ms)
|
||||||
|
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (19.283766ms)
|
||||||
|
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (12.623787ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.108573ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.506593ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (0.812213ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (12.42784ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.848665ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.496016ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (1.549448ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.82019ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.901995ms)
|
||||||
|
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.155995ms)
|
||||||
|
✔ the journal: a symlinked directory refuses and says it is a link (0.639875ms)
|
||||||
|
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.65835ms)
|
||||||
|
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.631409ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.442711ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (110.965551ms)
|
||||||
|
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 (275.770061ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (42.708073ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2171.791227ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.512192ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2858.176494
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (6.804459ms)
|
||||||
|
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.419003ms)
|
||||||
|
✔ completed smoke replies and ordinary questions are idle, not human blockers (1.05788ms)
|
||||||
|
✔ only an explicit first-line input request makes a finished reply waiting (0.306714ms)
|
||||||
|
✔ tool activity, user text, errors and unfinished turns override attention text (0.191863ms)
|
||||||
|
✔ STOP access failure is unknown, not absence, under a non-root identity (37.705417ms)
|
||||||
|
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.159431ms)
|
||||||
|
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.494166ms)
|
||||||
|
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (6.396242ms)
|
||||||
|
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.5883ms)
|
||||||
|
✔ server rescans connector discovery and refuses HTTP reply without transport (37.825985ms)
|
||||||
|
✔ connector session links and linked directories are not read (1.123235ms)
|
||||||
|
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (4.059555ms)
|
||||||
|
✔ CLI print uses the relaunch notice instead of old current preview (69.0968ms)
|
||||||
|
✔ connector owner and fixed task never inherit a native relaunch notice (2.309453ms)
|
||||||
|
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.447186ms)
|
||||||
|
✔ loadConfig: missing file throws ConfigError (1.74368ms)
|
||||||
|
✔ loadConfig: invalid JSON throws ConfigError (0.381839ms)
|
||||||
|
✔ loadConfig: missing dataRoot throws ConfigError (0.285709ms)
|
||||||
|
✔ loadConfig: relative dataRoot throws ConfigError (0.314598ms)
|
||||||
|
✔ loadConfig: valid config returns dataRoot (1.0148ms)
|
||||||
|
✔ findNewestSession: picks the newest by mtime among two files (0.633765ms)
|
||||||
|
✔ findNewestSession: finds files in nested subdirectories (0.368338ms)
|
||||||
|
✔ findNewestSession: returns null for a missing dir (0.150221ms)
|
||||||
|
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.912704ms)
|
||||||
|
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (2.004588ms)
|
||||||
|
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.831458ms)
|
||||||
|
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.688843ms)
|
||||||
|
✔ deriveState: full state table (0.267492ms)
|
||||||
|
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.485035ms)
|
||||||
|
✔ rule: newest entry is a tool result with no assistant text after it is working (0.417277ms)
|
||||||
|
✔ rule: a finished ordinary turn is idle, even if it says your move (0.419062ms)
|
||||||
|
✔ task: the first user message of the session, from text blocks (0.387843ms)
|
||||||
|
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.42006ms)
|
||||||
|
✔ task: no user message in the log means null (shown as unknown), never a guess (0.451738ms)
|
||||||
|
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.73409ms)
|
||||||
|
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (1.270857ms)
|
||||||
|
✔ scan: the written record carries task, workspace and activeProject (0.772335ms)
|
||||||
|
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.921072ms)
|
||||||
|
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.610015ms)
|
||||||
|
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (1.50323ms)
|
||||||
|
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.680322ms)
|
||||||
|
✔ loadRegistrations: a missing seatsDir gives empty lists (0.248283ms)
|
||||||
|
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (1.064486ms)
|
||||||
|
✔ 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.379473ms)
|
||||||
|
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.881035ms)
|
||||||
|
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.775753ms)
|
||||||
|
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.529713ms)
|
||||||
|
✔ scanAgent: ageSeconds is computed from the injected now (0.268697ms)
|
||||||
|
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.153852ms)
|
||||||
|
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.351278ms)
|
||||||
|
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.495575ms)
|
||||||
|
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (1.087513ms)
|
||||||
|
✔ scan: relative boardDir throws ConfigError (0.101032ms)
|
||||||
|
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (74.511371ms)
|
||||||
|
✔ CLI: missing config exits 2 with a refused: message (61.342575ms)
|
||||||
|
✔ CLI: unknown command exits 2 (77.494963ms)
|
||||||
|
✔ CLI: unknown --liveness value exits 2 (67.918675ms)
|
||||||
|
✔ panesRunPi: true when any trimmed line equals 'pi' (0.256634ms)
|
||||||
|
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.082198ms)
|
||||||
|
✔ tmuxIsAlive: a pane running pi is alive (0.226101ms)
|
||||||
|
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.089543ms)
|
||||||
|
✔ tmuxIsAlive: no such tmux session is not alive (0.077396ms)
|
||||||
|
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.087494ms)
|
||||||
|
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.107476ms)
|
||||||
|
✔ parsePanes: one pane per line, command and optional tab-separated path (0.091544ms)
|
||||||
|
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.079394ms)
|
||||||
|
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.112923ms)
|
||||||
|
✔ loadSeen: missing file returns {} (0.176853ms)
|
||||||
|
✔ loadSeen: invalid JSON throws ConfigError (0.215421ms)
|
||||||
|
✔ loadSeen: a JSON array throws ConfigError (0.270344ms)
|
||||||
|
✔ loadSeen: a non-string value throws ConfigError (0.199671ms)
|
||||||
|
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.401563ms)
|
||||||
|
✔ markSeen: seen false deletes the key (0.297031ms)
|
||||||
|
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.248251ms)
|
||||||
|
✔ markSeen: project containing '/' throws ConfigError (0.152326ms)
|
||||||
|
✔ markSeen: non-boolean seen throws ConfigError (0.128344ms)
|
||||||
|
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.341063ms)
|
||||||
|
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.251943ms)
|
||||||
|
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.252277ms)
|
||||||
|
✔ scanAgent: an error-state session with a matching mark is seen (0.294401ms)
|
||||||
|
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.565515ms)
|
||||||
|
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.231652ms)
|
||||||
|
✔ 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.765658ms)
|
||||||
|
✔ 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.684286ms)
|
||||||
|
✔ 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.186219ms)
|
||||||
|
✔ isLoopbackHost: recognizes loopback hosts (1.494333ms)
|
||||||
|
✔ isLoopbackHost: rejects non-loopback hosts (5.060193ms)
|
||||||
|
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.605504ms)
|
||||||
|
✔ startServer: serves page, healthz, and a rescanning /api/board (38.661997ms)
|
||||||
|
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (7.651859ms)
|
||||||
|
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.727ms)
|
||||||
|
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (63.239491ms)
|
||||||
|
✔ CLI: serve rejects a non-numeric --port with exit 2 (69.154042ms)
|
||||||
|
✔ CLI: scan still works after the async cli refactor (64.98425ms)
|
||||||
|
✔ CLI: live serve prints its URL and answers /healthz (69.66211ms)
|
||||||
|
✔ page.html: esc() escapes every HTML-significant character (0.655725ms)
|
||||||
|
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.302311ms)
|
||||||
|
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (1.985436ms)
|
||||||
|
✔ POST /api/seen with invalid JSON returns 400 (2.886182ms)
|
||||||
|
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.189468ms)
|
||||||
|
✔ POST /api/seen with a missing agent returns 400 (1.39406ms)
|
||||||
|
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.75052ms)
|
||||||
|
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (57.024677ms)
|
||||||
|
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.326067ms)
|
||||||
|
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.262888ms)
|
||||||
|
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.163235ms)
|
||||||
|
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.124708ms)
|
||||||
|
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.322965ms)
|
||||||
|
✔ 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.570669ms)
|
||||||
|
✔ 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 (30.544502ms)
|
||||||
|
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (30.97077ms)
|
||||||
|
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (25.810815ms)
|
||||||
|
✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (14.085827ms)
|
||||||
|
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (5.444484ms)
|
||||||
|
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.187269ms)
|
||||||
|
✔ 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.588031ms)
|
||||||
|
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.864235ms)
|
||||||
|
✔ 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.32637ms)
|
||||||
|
✔ Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers (8.426521ms)
|
||||||
|
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (31.234601ms)
|
||||||
|
✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (6.307879ms)
|
||||||
|
✔ every refusal code the reader can raise has an HTTP status (1.362529ms)
|
||||||
|
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (58.278992ms)
|
||||||
|
ℹ tests 124
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 124
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 739.046363
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
✔ W1: two processes acquire the same pair at once; exactly one claim (133.852204ms)
|
||||||
|
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (9.903435ms)
|
||||||
|
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (4.896975ms)
|
||||||
|
✔ W2: acquire while a claim is reserved or active refuses already-active (135.640382ms)
|
||||||
|
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (206.391742ms)
|
||||||
|
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (221.599112ms)
|
||||||
|
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (23.384355ms)
|
||||||
|
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (24.450943ms)
|
||||||
|
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (5.102513ms)
|
||||||
|
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5568.837794ms)
|
||||||
|
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (21997.530269ms)
|
||||||
|
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (155.448481ms)
|
||||||
|
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (92.901015ms)
|
||||||
|
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (227.675785ms)
|
||||||
|
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (210.868261ms)
|
||||||
|
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (234.593289ms)
|
||||||
|
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (36.707665ms)
|
||||||
|
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (112.154849ms)
|
||||||
|
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (36.795317ms)
|
||||||
|
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (89.467083ms)
|
||||||
|
✔ W11: the controller writes no session file; only the fake engine's own appends appear (21.736151ms)
|
||||||
|
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (54.569487ms)
|
||||||
|
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (59.350817ms)
|
||||||
|
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.251176ms)
|
||||||
|
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.178681ms)
|
||||||
|
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.751493ms)
|
||||||
|
✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2202.925091ms)
|
||||||
|
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (131.632977ms)
|
||||||
|
✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2166.353377ms)
|
||||||
|
✔ K4: two engines; force stop one; the other survives by independent observation (4254.701406ms)
|
||||||
|
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2151.481499ms)
|
||||||
|
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2249.888496ms)
|
||||||
|
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2151.391984ms)
|
||||||
|
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4379.099031ms)
|
||||||
|
✖ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (209.431279ms)
|
||||||
|
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (378.264182ms)
|
||||||
|
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (303.168036ms)
|
||||||
|
✔ K6: recover without proof, without confirmation, or with changed pins is refused (63.5942ms)
|
||||||
|
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (31.907608ms)
|
||||||
|
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (39.622245ms)
|
||||||
|
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3026.332595ms)
|
||||||
|
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (5.924081ms)
|
||||||
|
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (31.602414ms)
|
||||||
|
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (27.632187ms)
|
||||||
|
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (41.162844ms)
|
||||||
|
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (30.407461ms)
|
||||||
|
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (21.591465ms)
|
||||||
|
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (38.967149ms)
|
||||||
|
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (24.046296ms)
|
||||||
|
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.517583ms)
|
||||||
|
✔ 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.4388ms)
|
||||||
|
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (126.035815ms)
|
||||||
|
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (36.777384ms)
|
||||||
|
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (18.087403ms)
|
||||||
|
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (31.009894ms)
|
||||||
|
✔ 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 (39.751146ms)
|
||||||
|
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.347106ms)
|
||||||
|
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (30.498751ms)
|
||||||
|
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (50.879279ms)
|
||||||
|
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (43.41427ms)
|
||||||
|
✔ terminal: engine control characters are made visible; a lost connection refuses submit (29.777275ms)
|
||||||
|
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (1.192976ms)
|
||||||
|
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.508887ms)
|
||||||
|
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.635774ms)
|
||||||
|
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.203619ms)
|
||||||
|
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (426.881843ms)
|
||||||
|
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (57.446613ms)
|
||||||
|
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (82.431468ms)
|
||||||
|
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (133.261666ms)
|
||||||
|
✔ H4: self-takeover is refused (16.211065ms)
|
||||||
|
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (97.939907ms)
|
||||||
|
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (90.004566ms)
|
||||||
|
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (22.175376ms)
|
||||||
|
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (101.29259ms)
|
||||||
|
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (162.330347ms)
|
||||||
|
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (23.073584ms)
|
||||||
|
✔ H13: a retry with the same request ID and different text is refused (20.503171ms)
|
||||||
|
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (160.147974ms)
|
||||||
|
✔ H15: a revoked connection's command is refused; the revocation fence holds (70.472627ms)
|
||||||
|
✔ H16: a second controller for the same session refuses already-active; the first is untouched (20.528122ms)
|
||||||
|
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (57.87053ms)
|
||||||
|
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (60.931438ms)
|
||||||
|
✔ H18: two prompts before any native output: the second refuses busy; one engine write (13.031369ms)
|
||||||
|
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (120.535221ms)
|
||||||
|
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.769166ms)
|
||||||
|
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (474.647371ms)
|
||||||
|
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (402.296798ms)
|
||||||
|
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (335.17129ms)
|
||||||
|
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2389.207984ms)
|
||||||
|
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (459.593295ms)
|
||||||
|
✔ a plain conversation: catalogue row, one page, CHAT-01 records (11.130499ms)
|
||||||
|
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.570142ms)
|
||||||
|
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.07203ms)
|
||||||
|
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.811213ms)
|
||||||
|
✔ F1: an unreadable fork is never merged into another branch's history (1.359937ms)
|
||||||
|
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.4616ms)
|
||||||
|
✔ F1: a file whose entries are all unreadable shows a notice per line (1.05576ms)
|
||||||
|
✔ F2: a truncated trailing line marks the view incomplete, not an error (1.678293ms)
|
||||||
|
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.10643ms)
|
||||||
|
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.602312ms)
|
||||||
|
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.735978ms)
|
||||||
|
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (6.262886ms)
|
||||||
|
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (11.55803ms)
|
||||||
|
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (15.622698ms)
|
||||||
|
✔ F8: a file swapped for a symlink after the catalogue is refused (2.769422ms)
|
||||||
|
✔ F9: registrations never add or redirect a root (2.073453ms)
|
||||||
|
✔ F10: a header cwd naming another project is refused (5.419193ms)
|
||||||
|
✔ F11: parentSession renders with a marker and the parent is never opened (0.926192ms)
|
||||||
|
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (7.514641ms)
|
||||||
|
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (4.071265ms)
|
||||||
|
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.694144ms)
|
||||||
|
✔ F13: compaction is a marker in place, then the retained content (0.920202ms)
|
||||||
|
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (754.032088ms)
|
||||||
|
✔ fragments never cut a surrogate pair and keep an empty string (6.844356ms)
|
||||||
|
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (2.724415ms)
|
||||||
|
✔ unknown conversations, empty files and non-Pi files refuse (6.117025ms)
|
||||||
|
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (3.380886ms)
|
||||||
|
✔ a seat directory without search permission refuses that root, not the catalogue (3.162776ms)
|
||||||
|
✔ every page and cursor is a valid CHAT-01 record (906.130106ms)
|
||||||
|
✔ the engine pin holds for the installed package (4.095079ms)
|
||||||
|
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (329.078059ms)
|
||||||
|
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (367.946117ms)
|
||||||
|
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (86.798399ms)
|
||||||
|
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (48.062807ms)
|
||||||
|
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (26.288732ms)
|
||||||
|
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.777314ms)
|
||||||
|
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (32.219782ms)
|
||||||
|
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (23.411923ms)
|
||||||
|
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (116.896584ms)
|
||||||
|
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (43.955144ms)
|
||||||
|
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1542.958269ms)
|
||||||
|
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (19.768859ms)
|
||||||
|
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (81.992353ms)
|
||||||
|
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (15.892432ms)
|
||||||
|
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (17.178428ms)
|
||||||
|
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (33.514465ms)
|
||||||
|
✔ N10: fake conformance (32.242568ms)
|
||||||
|
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (31.604688ms)
|
||||||
|
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (19.419789ms)
|
||||||
|
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (64.723847ms)
|
||||||
|
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (30.077104ms)
|
||||||
|
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (32.547228ms)
|
||||||
|
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (23.737116ms)
|
||||||
|
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (15.431539ms)
|
||||||
|
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (30.152089ms)
|
||||||
|
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (113.257341ms)
|
||||||
|
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (134.703827ms)
|
||||||
|
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (84.206586ms)
|
||||||
|
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (17.434773ms)
|
||||||
|
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (15.350181ms)
|
||||||
|
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (13.781605ms)
|
||||||
|
✔ 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.472646ms)
|
||||||
|
ℹ tests 152
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 149
|
||||||
|
ℹ fail 3
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 31680.952099
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at packages/conversation/tests/cohort.test.mjs:139:1
|
||||||
|
✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2202.925091ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: the escaped child is a listed member
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r45/cand/packages/conversation/tests/cohort.test.mjs:151:12)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||||
|
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||||
|
generatedMessage: false,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: false,
|
||||||
|
expected: true,
|
||||||
|
operator: '==',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at packages/conversation/tests/cohort.test.mjs:176:1
|
||||||
|
✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2166.353377ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: the member ignored TERM
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r45/cand/packages/conversation/tests/cohort.test.mjs:190:12)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: false,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: false,
|
||||||
|
expected: true,
|
||||||
|
operator: '==',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
|
|
||||||
|
test at packages/conversation/tests/cohort.test.mjs:426:3
|
||||||
|
✖ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (209.431279ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: the member is alive across the crash
|
||||||
|
at TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r45/cand/packages/conversation/tests/cohort.test.mjs:431:12)
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||||
|
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||||
|
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||||
|
generatedMessage: false,
|
||||||
|
code: 'ERR_ASSERTION',
|
||||||
|
actual: false,
|
||||||
|
expected: true,
|
||||||
|
operator: '==',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.652717ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.095993ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.54469ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.452789ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (23.493731ms)
|
||||||
|
✔ 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.837264ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.33591ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.359428ms)
|
||||||
|
✔ authorize: open channel, listed user (2.063606ms)
|
||||||
|
✔ authorize: wrong guild (0.232728ms)
|
||||||
|
✔ authorize: no guild (DM) (0.315948ms)
|
||||||
|
✔ authorize: unlisted channel (0.197313ms)
|
||||||
|
✔ authorize: unknown channel, no info (1.556667ms)
|
||||||
|
✔ authorize: thread of listed parent (0.220056ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.194441ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.389674ms)
|
||||||
|
✔ authorize: unlisted user (1.236333ms)
|
||||||
|
✔ authorize: no author (0.268897ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.144111ms)
|
||||||
|
✔ authorize: system author (0.957145ms)
|
||||||
|
✔ authorize: the bot itself (0.097035ms)
|
||||||
|
✔ authorize: webhook (0.788496ms)
|
||||||
|
✔ authorize: mention channel without mention (0.216629ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.126082ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.200495ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.080933ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.118102ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.088496ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.075425ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.074497ms)
|
||||||
|
✔ authorize: not an object (0.071747ms)
|
||||||
|
✔ authorize: no id (0.080135ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.080836ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.068839ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.468829ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.321456ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (2.628137ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.454888ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.431805ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.282583ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.965346ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.284463ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (2.055307ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.46213ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (96.721294ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.578284ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (355.155821ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (234.212192ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (147.786226ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.908457ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.315136ms)
|
||||||
|
✔ 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 (23.053597ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.105767ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.598351ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.109963ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.312252ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.646579ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.105429ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.300546ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.045885ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.001041ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.015168ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.318708ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.996738ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.01912ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.559289ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.581434ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.157529ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.426272ms)
|
||||||
|
✔ 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.700785ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.075005ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.891254ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.128902ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.824301ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.098697ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.835755ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.21152ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (2.958133ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.624172ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.738849ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.688051ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (4.261033ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.451512ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (57.228583ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (37.77451ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (34.179736ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (344.382562ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (236.013539ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (104.867427ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.82586ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.632884ms)
|
||||||
|
✔ 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 (214.392388ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.922216ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.345609ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.426624ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.485032ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (24.737054ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (48.54618ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.303979ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.748646ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.64364ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.378703ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.846124ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.530317ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.4019ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (67.17032ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (43.499521ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (78.79627ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.291115ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (82.141136ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (122.090612ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (89.938936ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (263.800177ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (79.986947ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (94.991393ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (198.30173ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (798.143353ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.396422ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (76.864189ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.799364ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.415724ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (41.220051ms)
|
||||||
|
✔ 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 (343.762684ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.542473ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.632805ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.029429ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (43.572685ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (183.79061ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (93.704603ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.54971ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.184289ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.440721ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (3.141812ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (54.032714ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (45.592566ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (28.321045ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (72.192985ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (761.15839ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.576273ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.288617ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.766124ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.931996ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.950264ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.167307ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (2.841201ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.323203ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.946563ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (20.420568ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (8.293349ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (5.469769ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (6.703627ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.632777ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.143337ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.996595ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.440876ms)
|
||||||
|
✔ 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.550508ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.366425ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.222175ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.498032ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.692007ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.770283ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.802707ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.318209ms)
|
||||||
|
✔ tools: listing and search caps hold (9.386252ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.806032ms)
|
||||||
|
✔ 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.720349ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.213734ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.268516ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.495725ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.560525ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.735625ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.136088ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.907344ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1024.755556ms)
|
||||||
|
✔ 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.292763ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.240366ms)
|
||||||
|
✔ 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.695733ms)
|
||||||
|
✔ 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.408608ms)
|
||||||
|
ℹ tests 178
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 178
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2643.218847
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (126.128293ms)
|
||||||
|
✔ the raw path accepts nothing else, and refuses before curl runs (400.542688ms)
|
||||||
|
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (320.044861ms)
|
||||||
|
✔ the raw path base URL has no override (68.875532ms)
|
||||||
|
✔ the JSON path is unchanged, and JSON never falls through to the raw path (317.322975ms)
|
||||||
|
✔ a file that changes between the two reads refuses before curl runs, with or without a body (768.036835ms)
|
||||||
|
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (846.164709ms)
|
||||||
|
✔ real helper GET HTTP 200 preserves exit 0 without credentials (13.945172ms)
|
||||||
|
✔ real helper POST HTTP 201 preserves exit 0 without credentials (10.844397ms)
|
||||||
|
✔ real helper GET HTTP 403 preserves exit 1 without credentials (8.950885ms)
|
||||||
|
✔ fixture git subjects only, follow-ups and three session kinds (114.725705ms)
|
||||||
|
✔ text and JSON carry same numbers, open and truncated title (184.761853ms)
|
||||||
|
✔ missing credentials exit 2, no-issues never calls API and shows unknown (152.862609ms)
|
||||||
|
✔ empty range gives no rows and zero totals (104.702569ms)
|
||||||
|
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (113.97158ms)
|
||||||
|
✔ close-only issue included, even median, missing metadata stays unknown (118.811086ms)
|
||||||
|
✔ unique commits but per-issue links count multiple tags once each (129.964094ms)
|
||||||
|
✔ page cap refuses rather than silently undercounting (114.108135ms)
|
||||||
|
✔ bad API payload not JSON refuses (113.455091ms)
|
||||||
|
✔ bad API payload {} refuses (110.844375ms)
|
||||||
|
✔ bad API payload [{"number":1}] refuses (112.540073ms)
|
||||||
|
✔ partial or malformed session log refuses with location, not content (121.714506ms)
|
||||||
|
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (107.395892ms)
|
||||||
|
✔ no sessions is an empty table; symlink source refuses (160.016344ms)
|
||||||
|
✔ reads only refactor even when another branch is checked out (105.155384ms)
|
||||||
|
✔ invalid dates, reverse dates and duplicate options refuse (60.13167ms)
|
||||||
|
✔ T3 agent assignments do not count as human in Table 2 (106.620619ms)
|
||||||
|
✔ preamble parsing and issue number boundaries (0.513199ms)
|
||||||
|
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.149218ms)
|
||||||
|
✔ no closed issues with human messages means undefined ratio, not invented zero (0.207988ms)
|
||||||
|
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (234.383673ms)
|
||||||
|
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (521.013093ms)
|
||||||
|
✔ T3: a HOME with no database exits 1 and names --no-t3 (95.790127ms)
|
||||||
|
✔ T3: a file that is not a database exits 1 and names --no-t3 (101.911798ms)
|
||||||
|
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (149.00837ms)
|
||||||
|
✔ T3: an unmapped thread addressed as a seat exits 1 (117.853131ms)
|
||||||
|
✔ T3: a header to another thread id is not cross-checked (123.003429ms)
|
||||||
|
✔ T3: no project, or two, for this root exits 1 (325.128967ms)
|
||||||
|
✔ T3: a removed column exits 1 and names it (130.524629ms)
|
||||||
|
✔ T3: a missing table exits 1 and names it (111.192816ms)
|
||||||
|
✔ T3: a counted row with an unknown role exits 1 without its text (121.609112ms)
|
||||||
|
✔ T3: a counted row with non-text content exits 1 without its text (119.865902ms)
|
||||||
|
✔ T3: a counted row with an unparseable created_at exits 1 without its text (117.132498ms)
|
||||||
|
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (219.876231ms)
|
||||||
|
✔ T3: a human message with no event counts in humanWithoutEvent (123.337704ms)
|
||||||
|
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (217.196841ms)
|
||||||
|
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (216.275372ms)
|
||||||
|
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (60.752619ms)
|
||||||
|
✔ T3: a symlink at ~/.t3 exits 1 (87.471658ms)
|
||||||
|
✔ T3: a symlink at ~/.t3/userdata exits 1 (94.051514ms)
|
||||||
|
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (89.587247ms)
|
||||||
|
✔ T3: with --t3-db, a symlinked file or directory exits 1 (180.000997ms)
|
||||||
|
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (117.32872ms)
|
||||||
|
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (131.14448ms)
|
||||||
|
✔ T3 WAL: -wal without -shm in a writable directory is read (151.206765ms)
|
||||||
|
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (138.257245ms)
|
||||||
|
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (126.693454ms)
|
||||||
|
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5132.393612ms)
|
||||||
|
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.841977ms)
|
||||||
|
✔ an issue several rows close is expected closed only once all of them are done (0.491939ms)
|
||||||
|
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.38455ms)
|
||||||
|
✔ each owner of an in-progress or in-review row gets one liveness class (7.577627ms)
|
||||||
|
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.408923ms)
|
||||||
|
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.219338ms)
|
||||||
|
✔ the text section always ends in a count and a result, and never prints a full pass (0.328283ms)
|
||||||
|
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (23.340081ms)
|
||||||
|
✔ issue states: open list first, then the metric page, then at most 10 lookups (308.946454ms)
|
||||||
|
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (395.879857ms)
|
||||||
|
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (212.507314ms)
|
||||||
|
✔ a helper call past the deadline is killed with its child, and the call reports it (2009.325987ms)
|
||||||
|
✔ readQueue loads queue.json through the queue validator and refuses anything else (116.571991ms)
|
||||||
|
✔ protected changes list every in-range entry that changes a required or parked row (254.072185ms)
|
||||||
|
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (549.337582ms)
|
||||||
|
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (213.387035ms)
|
||||||
|
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (277.27238ms)
|
||||||
|
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (243.798875ms)
|
||||||
|
✔ --unsupported-runtime repeats once per seat and takes a seat name (309.141792ms)
|
||||||
|
✔ an unreadable config makes every owner invalid instead of passing them (129.721951ms)
|
||||||
|
ℹ tests 78
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 78
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 11441.871862
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
✔ pure resolution selects current default or explicit enrolled account (1.872151ms)
|
||||||
|
✔ scope is explicit, bounded and never inferred (1.57115ms)
|
||||||
|
✔ fork pin is preserved against default change, override, missing account and revocation (0.704128ms)
|
||||||
|
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.798909ms)
|
||||||
|
✔ only explicit synthetic credential forms and internal fixture stores admitted (5.36031ms)
|
||||||
|
✔ two concurrent workspaces of the same agent publish distinct complete private generations (161.818945ms)
|
||||||
|
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (42.892165ms)
|
||||||
|
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (65.595802ms)
|
||||||
|
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (77.784406ms)
|
||||||
|
✔ credential lock contention refuses without duplicate side effects (14.592571ms)
|
||||||
|
✔ symlinked pre-existing final target is refused and never followed (114.334933ms)
|
||||||
|
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.27403ms)
|
||||||
|
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (35.053473ms)
|
||||||
|
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (172.252181ms)
|
||||||
|
✔ refresh failure retains prior generation and store state (115.992743ms)
|
||||||
|
✔ refresh timeout retains prior generation and store state (175.78184ms)
|
||||||
|
✔ refresh malformed retains prior generation and store state (82.415683ms)
|
||||||
|
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (217.852814ms)
|
||||||
|
✔ invalid refresh options refuse before burning claim (42.224182ms)
|
||||||
|
✔ fixed fake process rotates both OAuth fields without mutating caller input (30.575047ms)
|
||||||
|
✔ concurrent isolated processes preserve separate provider credentials (70.011095ms)
|
||||||
|
✔ fake failure is refused with fixed diagnostics (23.924708ms)
|
||||||
|
✔ fake malformed is refused with fixed diagnostics (46.619583ms)
|
||||||
|
✔ fake timeout is refused with fixed diagnostics (103.993257ms)
|
||||||
|
✔ fake unchanged is refused with fixed diagnostics (24.438804ms)
|
||||||
|
✔ caller executable/environment injection is rejected before spawning (0.319162ms)
|
||||||
|
✔ valid fixture tree validates and lists without secrets (69.435664ms)
|
||||||
|
✔ unknown-field refuses (0.368527ms)
|
||||||
|
✔ invalid-id refuses uppercase and traversal shapes (0.267159ms)
|
||||||
|
✔ plain-http baseUrl requires allowInsecureTransport (0.299317ms)
|
||||||
|
✔ native provider rejects allowInsecureTransport (0.241272ms)
|
||||||
|
✔ unsupported credential type and kind refuse (0.159833ms)
|
||||||
|
✔ account provider-path mismatch refuses (0.109991ms)
|
||||||
|
✔ profile account refs must be provider/account shaped (0.219682ms)
|
||||||
|
✔ seat selection accepts fork pin field, validates account refs (0.208864ms)
|
||||||
|
✔ harness manifest id must equal executable (gate 1) (0.20672ms)
|
||||||
|
✔ CLI validate: duplicate provider id across files refuses (32.824146ms)
|
||||||
|
✔ CLI validate: missing referenced provider/account refuse (33.993274ms)
|
||||||
|
✔ CLI validate: broken JSON refuses without secret echo (28.59761ms)
|
||||||
|
✔ CLI usage errors exit 2 (59.606687ms)
|
||||||
|
✔ credential.json sibling presence does not break validation and is never read (68.475171ms)
|
||||||
|
✔ D1 missing, empty and structurally empty roots refuse, no list projection (194.464649ms)
|
||||||
|
✔ D1 required directory auth cannot be absent (64.16133ms)
|
||||||
|
✔ D1 required directory auth/providers cannot be absent (63.723043ms)
|
||||||
|
✔ D1 required directory auth/accounts cannot be absent (63.719991ms)
|
||||||
|
✔ D1 required directory auth/settings cannot be absent (63.575918ms)
|
||||||
|
✔ D1 required directory harnesses cannot be absent (63.409486ms)
|
||||||
|
✔ D1 root file and unreadable metadata refuse (109.21742ms)
|
||||||
|
✔ D2 no symlink traversal at auth/providers/openai-codex.json (56.303987ms)
|
||||||
|
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (65.600399ms)
|
||||||
|
✔ D2 no symlink traversal at auth/providers (54.789867ms)
|
||||||
|
✔ D2 no symlink traversal at auth (59.71393ms)
|
||||||
|
✔ D2 root and ancestor symlinks and lexical traversal refuse (171.864478ms)
|
||||||
|
✔ private filesystem modes enforced for root (67.447829ms)
|
||||||
|
✔ private filesystem modes enforced for auth (68.390099ms)
|
||||||
|
✔ private filesystem modes enforced for auth/providers/openai-codex.json (64.223364ms)
|
||||||
|
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (82.608422ms)
|
||||||
|
✔ D3 numeric version 1 only across all record kinds (1.62563ms)
|
||||||
|
✔ D4 nested unknown keys and missing per-kind required fields refuse (75.984001ms)
|
||||||
|
✔ D5 unenrolled default refuses even when account exists (78.872984ms)
|
||||||
|
✔ D6 provider/account credential type must match (68.848194ms)
|
||||||
|
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.482101ms)
|
||||||
|
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (178.618889ms)
|
||||||
|
✔ D9 malformed JSON diagnostics contain no content excerpt (73.23815ms)
|
||||||
|
✔ D10 missing metadata is missing-path, not invalid-json (67.801324ms)
|
||||||
|
✔ D10 library returns no partial entries on any invalid record (73.320294ms)
|
||||||
|
✔ null/scalar/array metadata refuses without stack or echo (264.773454ms)
|
||||||
|
✔ credential sibling is never opened, even when an unreadable symlink (36.871642ms)
|
||||||
|
✔ oversized metadata refuses before parsing (66.670211ms)
|
||||||
|
ℹ tests 69
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 69
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2358.596664
|
||||||
@@ -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 (1368.524395ms)
|
||||||
|
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1107.815614ms)
|
||||||
|
ℹ 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 (1053.859957ms)
|
||||||
|
ℹ 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 (1315.196413ms)
|
||||||
|
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1160.527505ms)
|
||||||
|
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1107.586369ms)
|
||||||
|
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1534.627974ms)
|
||||||
|
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (934.919964ms)
|
||||||
|
✔ F1: a shared-index change during the procedure is not committed (1106.384292ms)
|
||||||
|
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1034.784534ms)
|
||||||
|
✔ F1: a missing or a different hook refuses (881.067414ms)
|
||||||
|
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1206.112179ms)
|
||||||
|
✔ F1: the canary refuses a hook that git would not run (700.887362ms)
|
||||||
|
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1025.963075ms)
|
||||||
|
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (974.769984ms)
|
||||||
|
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (1049.631216ms)
|
||||||
|
✔ bootstrap: the archived tests and validator run outside any repository (965.953223ms)
|
||||||
|
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (917.850172ms)
|
||||||
|
✔ general: a queue write after the snapshot is not committed (1337.162804ms)
|
||||||
|
✔ general: a snapshot whose log does not extend the base refuses (1279.192635ms)
|
||||||
|
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (237.3212ms)
|
||||||
|
✔ general: environment overrides, a linked worktree and usage (596.349331ms)
|
||||||
|
✔ general: a queue path staged before the run refuses at step 1 (681.715215ms)
|
||||||
|
✔ general: HEAD's queue tests failing in the archive refuse (910.425138ms)
|
||||||
|
✔ genesis document serializes deterministically and replays (7.054109ms)
|
||||||
|
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (3.014575ms)
|
||||||
|
✔ a tampered result, receipt or viewSha fails replay (4.082269ms)
|
||||||
|
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.269546ms)
|
||||||
|
✔ add: defaults for an ordinary seat, privileged extras, refusals (5.909142ms)
|
||||||
|
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (7.861542ms)
|
||||||
|
✔ matrix: release, review round, changes requested and waiting-on-jason (20.322188ms)
|
||||||
|
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (17.233235ms)
|
||||||
|
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (36.694668ms)
|
||||||
|
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (4.077834ms)
|
||||||
|
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1594.6255ms)
|
||||||
|
✔ matrix: blocked keeps the claim and returns only to previousState (4.122793ms)
|
||||||
|
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.723419ms)
|
||||||
|
✔ field edits: who may change what (5.395692ms)
|
||||||
|
✔ set issues keeps a logged narrowing of closes (N10) (3.281972ms)
|
||||||
|
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.962337ms)
|
||||||
|
✔ every accepted text renders to nine cells on every row (N8) (24.65681ms)
|
||||||
|
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.566795ms)
|
||||||
|
✔ times and dates must be calendar values, not just the shape (2026-10-04) (2.504801ms)
|
||||||
|
✔ replay holds every op id to the caller's rule (N11) (9.205636ms)
|
||||||
|
✔ note: owner, listed reviewer or privileged; empty clears (1.274341ms)
|
||||||
|
✔ assign moves the claim with the owner; done clears it (2.904442ms)
|
||||||
|
✔ render is byte-stable and escapes pipes (0.588507ms)
|
||||||
|
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.744893ms)
|
||||||
|
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (25.079881ms)
|
||||||
|
✔ canonical args make a retry's identity independent of list order (0.330732ms)
|
||||||
|
✔ manifests, headings and blob ids (0.483773ms)
|
||||||
|
✔ the migration map: one queue-map block, exact keys (0.588646ms)
|
||||||
|
✔ every call but `queue` reaches the seat CLI exactly as before A2 (749.22435ms)
|
||||||
|
✔ `queue` reaches the queue CLI with the rest of the arguments (210.45014ms)
|
||||||
|
✔ the pre-A2 fixture is the script A2 changed (0.378694ms)
|
||||||
|
✔ acquire publishes the record by link; release removes only its own lock (8.169093ms)
|
||||||
|
✔ a kill between the temp write and the link leaves no lock (71.284163ms)
|
||||||
|
✔ a short or failed temp write refuses and leaves no lock and no temp (4.246816ms)
|
||||||
|
✔ a link error other than EEXIST refuses (2.629069ms)
|
||||||
|
✔ an error after the link releases the lock: unreadable gate, failing temp stat (5.709139ms)
|
||||||
|
✔ a release that fails on a gate path is reported, never a stack trace (P1) (7.955663ms)
|
||||||
|
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10073.647915ms)
|
||||||
|
✔ two concurrent unlockers: the second refuses on the gate (27.810421ms)
|
||||||
|
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (1.976767ms)
|
||||||
|
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (1.948203ms)
|
||||||
|
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (2.299562ms)
|
||||||
|
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (3.735687ms)
|
||||||
|
✔ the same pid and start on a different boot is mismatch (0.45282ms)
|
||||||
|
✔ a foreign host is unknown whatever the local pid says; unlock refuses (58.060779ms)
|
||||||
|
✔ unreadable /proc: classification is unknown and acquire refuses (1.143527ms)
|
||||||
|
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.280584ms)
|
||||||
|
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (4.074518ms)
|
||||||
|
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (2.189275ms)
|
||||||
|
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (1.390838ms)
|
||||||
|
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (3.773733ms)
|
||||||
|
✔ the migration map validates and renders the golden genesis table (5.562141ms)
|
||||||
|
✔ the marked QUEUE.md holds every row and parked item between its markers (1.878447ms)
|
||||||
|
✔ map-check reports each kind of drift (7.292576ms)
|
||||||
|
✔ a request posts once as the requester; a retry sends nothing (688.350161ms)
|
||||||
|
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (3473.819954ms)
|
||||||
|
✔ the pre-send checks: GET user must name the requester, under the deadline (1226.019299ms)
|
||||||
|
✔ the lead's request refuses a token for login sage (572.249013ms)
|
||||||
|
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (503.984503ms)
|
||||||
|
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1722.410185ms)
|
||||||
|
✔ a same-op retry after a kill sends nothing, even with a stale view (2421.424105ms)
|
||||||
|
✔ a held lock at the outcome exits 3 and names what the transport said (570.240504ms)
|
||||||
|
✔ late outcomes: after an abandon, and after a resolve with the same or another id (1743.338011ms)
|
||||||
|
✔ resolve checks the comment: issue, markers, round, candidate and author (1601.710919ms)
|
||||||
|
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (576.148221ms)
|
||||||
|
✔ validateRow checks a request round's shape, which every replayed entry must keep (391.275684ms)
|
||||||
|
✔ request, changes, a new candidate, approval: every round pinned; no review files (1594.71266ms)
|
||||||
|
✔ a row with no reviewers opens a round that posts nothing (1017.17584ms)
|
||||||
|
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1232.03807ms)
|
||||||
|
✔ semantics: v1 entries replay as before; review entries need v2 (404.516761ms)
|
||||||
|
✔ set reviewers refuses the row's owner (2026-09-28) (294.923201ms)
|
||||||
|
✔ the owner records no verdict, even as a listed reviewer (377.194736ms)
|
||||||
|
✔ a request comment over the length limit is not sent (419.581132ms)
|
||||||
|
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (508.094636ms)
|
||||||
|
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2142.907828ms)
|
||||||
|
✔ genesis: refusals before anything is written (512.662775ms)
|
||||||
|
✔ genesis: the map must be committed, well formed, with committed briefs and seats (661.798245ms)
|
||||||
|
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (570.512249ms)
|
||||||
|
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (326.662108ms)
|
||||||
|
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (715.634667ms)
|
||||||
|
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (534.717688ms)
|
||||||
|
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (710.319686ms)
|
||||||
|
✔ a retried add returns the id it first allocated, after reassignment and after done (810.716943ms)
|
||||||
|
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (529.80669ms)
|
||||||
|
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (931.381579ms)
|
||||||
|
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (656.148184ms)
|
||||||
|
✔ add, set reviewers and assign refuse the row's owner as a reviewer (445.647197ms)
|
||||||
|
✔ the working-brief check: a changed working copy refuses the start and flags next (651.123258ms)
|
||||||
|
✔ next: resume first, then nothing for an idle seat; needs a seat (323.006358ms)
|
||||||
|
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (630.609898ms)
|
||||||
|
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1084.349585ms)
|
||||||
|
✔ a hand edit to queue.json refuses every verb, reads included (594.164641ms)
|
||||||
|
✔ verify and render --check leave bytes and mtimes unchanged (389.003599ms)
|
||||||
|
✔ render is byte-stable across runs and repositories (264.819899ms)
|
||||||
|
✔ snapshot and verify --snapshot (770.221731ms)
|
||||||
|
✔ usage errors exit 4 (681.438982ms)
|
||||||
|
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (232.825599ms)
|
||||||
|
✔ a rename failure: nothing visible, temp removed (145.840545ms)
|
||||||
|
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (175.377624ms)
|
||||||
|
✔ a directory fsync failure, then sync names the op (334.200058ms)
|
||||||
|
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (146.849109ms)
|
||||||
|
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (154.073374ms)
|
||||||
|
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (159.117564ms)
|
||||||
|
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (152.332799ms)
|
||||||
|
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (148.389128ms)
|
||||||
|
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (151.546104ms)
|
||||||
|
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (115.474484ms)
|
||||||
|
✔ a view write that fails keeps the op and reports a stale view (114.610379ms)
|
||||||
|
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (615.523099ms)
|
||||||
|
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (653.62181ms)
|
||||||
|
✔ SIGKILL after the witness, before the view: the stale refusal names the op (620.210512ms)
|
||||||
|
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (665.935427ms)
|
||||||
|
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (263.734749ms)
|
||||||
|
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (920.323427ms)
|
||||||
|
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (208.428956ms)
|
||||||
|
✔ a header edit during a write: the op stands, the view write is skipped with a warning (133.27623ms)
|
||||||
|
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (142.279617ms)
|
||||||
|
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (138.591803ms)
|
||||||
|
✔ a writer paused before and after the witness rename: readers see a tail, then a match (139.175204ms)
|
||||||
|
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (518.066437ms)
|
||||||
|
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (465.309105ms)
|
||||||
|
✔ the platform check refuses other filesystems (130.878385ms)
|
||||||
|
✔ tmpfs passes only a test layer that allows it (N5) (156.961291ms)
|
||||||
|
✔ unlock keeps a multi-line lock record on stdout (P3) (222.786106ms)
|
||||||
|
ℹ tests 148
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 148
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 24575.018457
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
✔ resolveSeat: by name under --repo resolves the repo layout (1.251101ms)
|
||||||
|
✔ resolveSeat: by path resolves the fleet layout (0.314635ms)
|
||||||
|
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.710458ms)
|
||||||
|
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.647878ms)
|
||||||
|
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.790986ms)
|
||||||
|
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.269425ms)
|
||||||
|
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.156407ms)
|
||||||
|
✔ CLI launch: registers, execs the fake launch script, and passes args through (30.222001ms)
|
||||||
|
✔ CLI launch: --harness lands in the record (32.913646ms)
|
||||||
|
✔ CLI launch: the launch script's own exit code passes through (29.484011ms)
|
||||||
|
✔ CLI launch: relaunching a seat rewrites the one registration record (61.925976ms)
|
||||||
|
✔ CLI launch: omitting --task records an empty string, not null (27.792171ms)
|
||||||
|
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (82.182296ms)
|
||||||
|
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (148.150447ms)
|
||||||
|
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.786055ms)
|
||||||
|
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.674817ms)
|
||||||
|
✔ 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.868062ms)
|
||||||
|
✔ 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 (11.536829ms)
|
||||||
|
✔ 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 (311.700226ms)
|
||||||
|
ℹ tests 19
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 19
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 811.678252
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
✔ the boot config is checked before anything starts (31.406315ms)
|
||||||
|
✔ a business with no tracker entry refuses task verbs (10.50476ms)
|
||||||
|
✔ credential.expiring and .expired are recorded once per instance (31.40099ms)
|
||||||
|
✔ a token file that changes on disk records credential.changed (12.76132ms)
|
||||||
|
✔ autostart polls, reconciles and retries a startup the tracker was down for (36.972972ms)
|
||||||
|
✔ a refusal a restart must clear is not retried by the poll (11.379556ms)
|
||||||
|
✔ a poll that fires while two are queued is dropped (15.039808ms)
|
||||||
|
✔ close waits for a running verb and refuses one that has not started (43.104982ms)
|
||||||
|
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.83167ms)
|
||||||
|
✔ every published port is on 127.0.0.1, and no secret is in the file (0.311917ms)
|
||||||
|
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (106.64679ms)
|
||||||
|
✔ the recorded task bodies pass the checks S3 applies to every read (0.5836ms)
|
||||||
|
✔ the client works against the fake over real HTTP with the platform fetch (103.710016ms)
|
||||||
|
✔ a correct install starts, and the first reconcile records tasks that already exist (26.761043ms)
|
||||||
|
✔ verbs refuse while a business is starting and after startup refused it (17.920027ms)
|
||||||
|
✔ startup refuses a token that can do more than its role needs (38.784719ms)
|
||||||
|
✔ startup refuses an unsupported version and flags an untested one (29.454798ms)
|
||||||
|
✔ startup refuses a board that the runbook did not install (39.884452ms)
|
||||||
|
✔ startup refuses a project the sync bot cannot read (6.805487ms)
|
||||||
|
✔ startup refuses a configured label the pm bot cannot see (8.89736ms)
|
||||||
|
✔ startup refuses an expired credential and a missing sync credential (17.507495ms)
|
||||||
|
✔ an unreachable tracker refuses with tracker-unavailable (7.515047ms)
|
||||||
|
✔ an edit in the UI is recorded once, with the fields that changed (88.08522ms)
|
||||||
|
✔ a move between open buckets is seen on the board, though updated does not change (39.239909ms)
|
||||||
|
✔ a person's comment is counted and a bot's is not (70.85552ms)
|
||||||
|
✔ the hourly reconcile catches a comment through comment_count (79.111948ms)
|
||||||
|
✔ a task closed in the UI leaves the open view with its done bucket (112.694106ms)
|
||||||
|
✔ a task that leaves the board is recorded as deleted, moved or out of reach (66.256236ms)
|
||||||
|
✔ a poll that read before a verb wrote does not overwrite the verb (36.027847ms)
|
||||||
|
✔ a tracker fault during a tick is reported and the next tick catches up (36.364956ms)
|
||||||
|
✔ a malformed answer refuses the tick with tracker-shape (13.83003ms)
|
||||||
|
✔ no token value reaches the database, the log or a refusal (56.431652ms)
|
||||||
|
✔ the first look at a task counts only comments inside the window (35.769886ms)
|
||||||
|
✔ task.create needs a recorded human request and a requirement id (41.974426ms)
|
||||||
|
✔ only labels named in the business file can be written (26.40169ms)
|
||||||
|
✔ task.schedule sets and clears a due date and relations (34.940101ms)
|
||||||
|
✔ assign and reassign move the role bots and record task.assigned (59.327811ms)
|
||||||
|
✔ task.update.assigned is for the assignee and records task.state (53.98079ms)
|
||||||
|
✔ a wrong expected digest records task.conflict and writes nothing (17.784255ms)
|
||||||
|
✔ a cross-role verb needs a resolved decision, used once (22.522257ms)
|
||||||
|
✔ task.close needs a verdict; after it every verb refuses with task-done (23.350917ms)
|
||||||
|
✔ a lost answer is settled by a re-read and never retried (25.303349ms)
|
||||||
|
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (35.162388ms)
|
||||||
|
✔ a task the sync bot cannot read refuses and records nothing (15.624289ms)
|
||||||
|
✔ verbs and polls for one business run one at a time (91.847876ms)
|
||||||
|
✔ a due date with milliseconds is written to the second (65.82116ms)
|
||||||
|
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (44.19338ms)
|
||||||
|
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (16.809815ms)
|
||||||
|
✔ a create whose final read fails succeeds and records task.created (35.830588ms)
|
||||||
|
✔ an edit between the last write and the final read shows as external on the next poll (38.316051ms)
|
||||||
|
✔ task.created is recorded when a later label write fails (15.830074ms)
|
||||||
|
ℹ tests 51
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 51
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 776.554894
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2593.877023ms)
|
||||||
|
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
|
||||||
|
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2819.101922ms)
|
||||||
|
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2487.022264ms)
|
||||||
|
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1409.838855ms)
|
||||||
|
✔ conversation view: a newer session with no readable history keeps the marker (1033.313886ms)
|
||||||
|
✔ conversation view: seats without history say so and offer no reply (510.394246ms)
|
||||||
|
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2198.027385ms)
|
||||||
|
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53388.794719ms)
|
||||||
|
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2301.993318ms)
|
||||||
|
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21973.89591ms)
|
||||||
|
✔ loopback host and board origin fail closed (10.966556ms)
|
||||||
|
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (111.519321ms)
|
||||||
|
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (60.651787ms)
|
||||||
|
✔ unreachable board reports URL; CLI rejects unsupported options (969.115867ms)
|
||||||
|
ℹ tests 14
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 14
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 53754.387454
|
||||||
@@ -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 '521597bbe0cf51a0a1aa3b7a16f13fc7b59c98c6'.
|
||||||
|
|
||||||
|
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 '521597bbe0cf51a0a1aa3b7a16f13fc7b59c98c6'.
|
||||||
|
|
||||||
|
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: 4117866 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/r45/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,42 @@
|
|||||||
|
r1-cand node-business exit 0
|
||||||
|
r1-cand node-bus exit 0
|
||||||
|
r1-cand node-cli exit 0
|
||||||
|
r1-cand node-control-board exit 0
|
||||||
|
r1-cand node-conversation exit 1
|
||||||
|
r1-cand node-discord exit 0
|
||||||
|
r1-cand node-ledger exit 0
|
||||||
|
r1-cand node-mosaic exit 0
|
||||||
|
r1-cand node-queue exit 0
|
||||||
|
r1-cand node-seat exit 0
|
||||||
|
r1-cand node-tasks exit 0
|
||||||
|
r1-cand node-webui exit 0
|
||||||
|
r1-cand suite-auth exit 0 load 5.12
|
||||||
|
r1-cand suite-conductor exit 0 load 5.03
|
||||||
|
r1-cand suite-config exit 0 load 4.63
|
||||||
|
r1-cand suite-discord exit 0 load 4.58
|
||||||
|
r1-cand suite-extension-package exit 0 load 4.58
|
||||||
|
r1-cand suite-foundation exit 0 load 3.87
|
||||||
|
r1-cand suite-queue exit 0 load 5.10
|
||||||
|
r1-cand suite-release exit 0 load 5.10
|
||||||
|
r1-cand suite-task exit 1 load 5.10
|
||||||
|
r1-base node-business exit 0
|
||||||
|
r1-base node-bus exit 0
|
||||||
|
r1-base node-cli exit 0
|
||||||
|
r1-base node-control-board exit 0
|
||||||
|
r1-base node-conversation exit 1
|
||||||
|
r1-base node-discord exit 0
|
||||||
|
r1-base node-ledger exit 0
|
||||||
|
r1-base node-mosaic exit 0
|
||||||
|
r1-base node-queue exit 0
|
||||||
|
r1-base node-seat exit 0
|
||||||
|
r1-base node-tasks exit 0
|
||||||
|
r1-base node-webui exit 0
|
||||||
|
r1-base suite-auth exit 0 load 11.24
|
||||||
|
r1-base suite-conductor exit 0 load 10.66
|
||||||
|
r1-base suite-config exit 0 load 10.66
|
||||||
|
r1-base suite-discord exit 0 load 11.32
|
||||||
|
r1-base suite-extension-package exit 0 load 11.32
|
||||||
|
r1-base suite-foundation exit 0 load 6.34
|
||||||
|
r1-base suite-queue exit 0 load 5.93
|
||||||
|
r1-base suite-release exit 0 load 5.54
|
||||||
|
r1-base suite-task exit 1 load 5.54
|
||||||
@@ -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,32 @@
|
|||||||
|
N2 killed (fail 1, cancelled 0)
|
||||||
|
N4 killed (fail 1, cancelled 0)
|
||||||
|
N5 killed (fail 1, cancelled 0)
|
||||||
|
M28 killed (fail 1, cancelled 0)
|
||||||
|
G150 killed (fail 1, cancelled 0)
|
||||||
|
G144 killed (fail 1, cancelled 0)
|
||||||
|
F2a killed (fail 1, cancelled 0)
|
||||||
|
F2b killed (fail 1, cancelled 0)
|
||||||
|
F2c killed (fail 2, cancelled 0)
|
||||||
|
F2d killed (fail 1, cancelled 0)
|
||||||
|
F2e killed (fail 1, cancelled 0)
|
||||||
|
F2f killed (fail 1, cancelled 0)
|
||||||
|
J4a killed (fail 1, cancelled 0)
|
||||||
|
J4b killed (fail 1, cancelled 0)
|
||||||
|
J4c killed (fail 1, cancelled 0)
|
||||||
|
J4d killed (fail 1, cancelled 0)
|
||||||
|
E1 killed (fail 1, cancelled 0)
|
||||||
|
E2 killed (fail 1, cancelled 0)
|
||||||
|
X1 killed (fail 1, cancelled 0)
|
||||||
|
X2 SURVIVED
|
||||||
|
X3 killed (fail 3, cancelled 0)
|
||||||
|
X4 killed (fail 1, cancelled 0)
|
||||||
|
X5 killed (fail 1, cancelled 0)
|
||||||
|
X6 killed (fail 1, cancelled 0)
|
||||||
|
X7 killed (fail 1, cancelled 0)
|
||||||
|
X8 killed (fail 1, cancelled 0)
|
||||||
|
X9 SURVIVED
|
||||||
|
X10 killed (fail 1, cancelled 0)
|
||||||
|
X11 killed (fail 1, cancelled 0)
|
||||||
|
X12 killed (fail 3, cancelled 0)
|
||||||
|
X13 killed (fail 2, cancelled 0)
|
||||||
|
X14 SURVIVED
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
v24.21.0
|
||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (169.210077ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (220.995357ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (221.951051ms)
|
||||||
|
✔ decide prints a declining choice as declining (167.525958ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (127.301829ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (161.285204ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (122.126437ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (129.876238ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (119.673429ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (166.691645ms)
|
||||||
|
✔ agents and tasks print through the broker (186.413662ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (13.333788ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (201.980972ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (114.238503ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (83.289259ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (75.913435ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (120.454836ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (173.234133ms)
|
||||||
|
✔ empty views say so (2.235721ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (2.711433ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.326825ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1420.380971ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (526.895008ms)
|
||||||
|
✔ a second host for the same data root refuses with exit 3 while the first runs (205.432629ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (380.380756ms)
|
||||||
|
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (401.224717ms)
|
||||||
|
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (193.95973ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (30.977871ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (206.813112ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (314.80728ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (148.547702ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (917.572739ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (55.222104ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (174.293637ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (189.689945ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (185.5709ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.474682ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (228.801997ms)
|
||||||
|
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (185.603648ms)
|
||||||
|
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (160.573693ms)
|
||||||
|
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (128.228554ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (155.668925ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (167.201859ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (2.445857ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (151.557087ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (33.621183ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (88.02178ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (24.4463ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (19.133951ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (5.649955ms)
|
||||||
|
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (21.592611ms)
|
||||||
|
✔ the journal: a symlinked directory refuses and says it is a link (2.071451ms)
|
||||||
|
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (3.932517ms)
|
||||||
|
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (2.475167ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.631769ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (124.297708ms)
|
||||||
|
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 (976.590424ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (209.432574ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2384.766754ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.613505ms)
|
||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (12.852656ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (5.782138ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.867481ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.677824ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (44.404919ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (24.440255ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (15.489524ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (27.563545ms)
|
||||||
|
✔ authorize: open channel, listed user (16.150485ms)
|
||||||
|
✔ authorize: wrong guild (0.28161ms)
|
||||||
|
✔ authorize: no guild (DM) (0.225398ms)
|
||||||
|
✔ authorize: unlisted channel (1.378769ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.846771ms)
|
||||||
|
✔ authorize: thread of listed parent (0.639398ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.491093ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.248551ms)
|
||||||
|
✔ authorize: unlisted user (0.249877ms)
|
||||||
|
✔ authorize: no author (0.556257ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.192138ms)
|
||||||
|
✔ authorize: system author (0.166337ms)
|
||||||
|
✔ authorize: the bot itself (0.116806ms)
|
||||||
|
✔ authorize: webhook (0.113884ms)
|
||||||
|
✔ authorize: mention channel without mention (0.188505ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.203378ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.169766ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.112607ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.139715ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.12079ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.100549ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.242092ms)
|
||||||
|
✔ authorize: not an object (1.111897ms)
|
||||||
|
✔ authorize: no id (0.1492ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.110482ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.086483ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.684236ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.216255ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (11.883208ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (7.763507ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.506707ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.64819ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (5.565414ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (7.762208ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (5.293905ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.823072ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (220.470979ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.235281ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (885.135928ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (415.829812ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (277.976045ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (3.601177ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (2.952646ms)
|
||||||
|
✔ 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 (37.062415ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (48.925104ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.735505ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (4.826431ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (7.131981ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (7.448548ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.250691ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (6.149321ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (6.309747ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (12.979224ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (48.344945ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (79.624664ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (12.828338ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (12.537168ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (9.427092ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (8.457426ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.386104ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.981586ms)
|
||||||
|
✔ 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.448548ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (5.770748ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (16.068602ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.733839ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.840293ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (10.650128ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (2.780979ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (4.29911ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.58776ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (8.457708ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.601151ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (4.966235ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.068728ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.97972ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (118.279655ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (90.864625ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (82.471638ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (374.102779ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (325.526012ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (125.370647ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (266.818641ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (173.810103ms)
|
||||||
|
✔ 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 (215.100324ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (622.159336ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (3.420612ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (1.316421ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (466.085165ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (50.432037ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (59.967268ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.410353ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (2.021625ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.699336ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.428487ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.052509ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.679693ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.502568ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (285.019903ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (136.347733ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (272.428902ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (1.350846ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (397.671011ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (284.970897ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (226.383507ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (405.146352ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (110.552937ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (133.506908ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (356.776939ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (608.652604ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (9.207667ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (137.107672ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.449643ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (6.372436ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (81.131754ms)
|
||||||
|
✔ 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 (821.907635ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (1.169369ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (44.588245ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (3.410371ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (102.949689ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (258.084232ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (176.49723ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (1.053503ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (10.557984ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.7748ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (4.592249ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (75.563709ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (61.586462ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (56.812654ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (207.860034ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (1510.849171ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (5.531414ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.650468ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.932379ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.954723ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (2.00831ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (10.254659ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (4.714592ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.900566ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.163685ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (84.247403ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (24.969462ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (14.721499ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (17.167329ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (3.160519ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (9.572244ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1042.749224ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (6.688099ms)
|
||||||
|
✔ 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 (13.997535ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (8.115785ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (1.376507ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (7.689174ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (8.854561ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (17.340196ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (9.218508ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (10.983915ms)
|
||||||
|
✔ tools: listing and search caps hold (32.381356ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (7.784107ms)
|
||||||
|
✔ 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 (35.08534ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (6.387642ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (6.48996ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (26.868965ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (15.200985ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (6.559184ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (13.287236ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (4.204019ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1105.741841ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (16.202457ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.804354ms)
|
||||||
|
✔ 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 (10.647487ms)
|
||||||
|
✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (8.722026ms)
|
||||||
|
ℹ tests 238
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 238
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 5109.413025
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
PROBE T1 sends at +0s +30s +90s +210s +450s
|
||||||
|
PROBE T1 gave-up at +450s = 7.5 min
|
||||||
|
PROBE T2 after 4 h: outcomes refused,refused,refused,refused,refused,gave-up
|
||||||
|
PROBE T2 sends 5
|
||||||
|
PROBE T3 sends before restart 2 after one tick post-restart 3 (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":"624d0407-56e6-4686-b5af-21dbc41002fb","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 (86.95343ms)
|
||||||
|
✔ F2-T2 binding fixed after 60 min: the DM never lands (172.659536ms)
|
||||||
|
✔ F2-T3 restart drops the backoff: next attempt is immediate (60.500209ms)
|
||||||
|
✔ F2-T4 500s for 6 h: no gave-up (243.575372ms)
|
||||||
|
✔ F2-T5 digest refusals: retried, never gave-up (75.006372ms)
|
||||||
|
✔ J4-A1 append does not type-check: a numeric messageId bricks the next open (61.251173ms)
|
||||||
|
✔ J4-A2 a line the row 39 writer produced still opens (57.483971ms)
|
||||||
|
ℹ tests 7
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 7
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 844.895562
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
nocb connected before send true
|
||||||
|
nocb process exit code 1
|
||||||
|
node:events:505
|
||||||
|
throw er; // Unhandled 'error' event
|
||||||
|
^
|
||||||
|
|
||||||
|
-- exit 0
|
||||||
|
nocb connected before send true
|
||||||
|
nocb process exit code 1
|
||||||
|
node:events:505
|
||||||
|
throw er; // Unhandled 'error' event
|
||||||
|
^
|
||||||
|
|
||||||
|
-- exit 0
|
||||||
|
nocb connected before send true
|
||||||
|
nocb process exit code 1
|
||||||
|
node:events:505
|
||||||
|
throw er; // Unhandled 'error' event
|
||||||
|
^
|
||||||
|
|
||||||
|
-- exit 0
|
||||||
|
cb connected before send true
|
||||||
|
cb callback got EPIPE
|
||||||
|
cb process exit code 3
|
||||||
|
-- exit 0
|
||||||
|
cb connected before send true
|
||||||
|
cb callback got EPIPE
|
||||||
|
cb process exit code 3
|
||||||
|
-- exit 0
|
||||||
|
cb connected before send true
|
||||||
|
cb callback got EPIPE
|
||||||
|
cb process exit code 3
|
||||||
|
-- exit 0
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# Row 45, S4 follow-up, round 1 review (Filbert)
|
||||||
|
|
||||||
|
Issue #1527, request comment 26869, queue rev 207. Packet:
|
||||||
|
`agents/rocko/work/s4-follow-up/BUILD.md`. Candidate: manifest
|
||||||
|
`candidate-manifest.sha256`, sha256
|
||||||
|
`b329fdcbdf8cb62659f239359570dfa9771559e3112b743df55429244769ed14`, 8
|
||||||
|
files, over `521597bb` with `build.patch` (sha256
|
||||||
|
`4ed9ff61b94b4c9d2426e5703c2e6c0dbcd6bc0c64a88d59347a8b47f4933408`).
|
||||||
|
Brief: `docs/plans/2026-10-09_s4-follow-up-and-cohort.md`, "S4 follow-up".
|
||||||
|
Ruling: lead decision 72. Second reviewer: Darkwing (comment 26872).
|
||||||
|
This verdict: comment 26880.
|
||||||
|
|
||||||
|
Verdict: **changes.** There is one blocker, B1. With the current backoff,
|
||||||
|
five definite refusals arrive within 7.5 minutes, so a binding typo gives
|
||||||
|
up every blocking DM long before anyone can fix it. Decision 72 sets the
|
||||||
|
limit so that a typo fixed "within a few hours" is ridden out. I also ask
|
||||||
|
for the send callback (R1) in round 2. Everything else is a note.
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
- Detached worktrees at `521597bb` under `~/filbert-scratch/r45/`: base,
|
||||||
|
candidate and a mutant tree. `git apply build.patch`, then `sha256sum -c`:
|
||||||
|
8 OK in both patched trees, and 8 OK in the mutant tree after the run.
|
||||||
|
- `gate.sh` runs every package's node tests and every `scripts/test-*.sh`,
|
||||||
|
one at a time, in the candidate and then the base, and tees each output.
|
||||||
|
`DOCKER_HOST` points at a socket that doesn't exist.
|
||||||
|
- `probe/probe.test.mjs` drives `createNotifier` with a fake clock and a
|
||||||
|
fake Discord (F2-T1 to T5, J4-A1 and A2).
|
||||||
|
- `probe/sendwindow.mjs` checks what `ChildProcess.send` does when the peer
|
||||||
|
is dead but this process has not yet seen the disconnect.
|
||||||
|
- `mutants.sh` reruns Rocko's 18 and adds X1 to X14. A run counts as
|
||||||
|
killed when a test fails or is cancelled, or the outer timeout fires.
|
||||||
|
- Node 24.21.0: `node:24`, no network, the tree mounted read-only, the host
|
||||||
|
uid.
|
||||||
|
|
||||||
|
## Suites
|
||||||
|
|
||||||
|
| Suite | Candidate | Base |
|
||||||
|
|---|---|---|
|
||||||
|
| node cli | 60/60 | 49/49 |
|
||||||
|
| node cli + discord (Node 24.21.0) | 238/238 | n/a |
|
||||||
|
| node discord | 178/178 | 178/178 |
|
||||||
|
| node bus | 67/67 | 67/67 |
|
||||||
|
| node business | 60/60 | 60/60 |
|
||||||
|
| node conversation | 149 pass, 3 fail | 149 pass, 3 fail |
|
||||||
|
| node control-board, ledger, mosaic, queue, seat, tasks, webui | all green | all green |
|
||||||
|
| test-auth, conductor, config, discord, extension-package, foundation, queue | all green | all green |
|
||||||
|
| test-release | 4/4, Docker cases skipped | same |
|
||||||
|
| test-task | 26 pass, 2 fail | 26 pass, 2 fail |
|
||||||
|
|
||||||
|
The three conversation failures are K1, K3 and K10 in both trees (row 46).
|
||||||
|
Rocko saw 150/2 on the base, so one of the three passes there
|
||||||
|
intermittently. test-task fails "user recall run succeeds" and "recalled
|
||||||
|
user name" in both trees.
|
||||||
|
|
||||||
|
## B1 (blocking): five refusals take 7.5 minutes, not a few hours
|
||||||
|
|
||||||
|
The backoff for a refusal is the same as for an unknown outcome: 30 s,
|
||||||
|
doubling. Probe F2-T1 polls every `POLL_MS` with Discord answering 403
|
||||||
|
every time:
|
||||||
|
|
||||||
|
```
|
||||||
|
sends at +0s +30s +90s +210s +450s
|
||||||
|
gave-up at +450s = 7.5 min
|
||||||
|
```
|
||||||
|
|
||||||
|
Probe F2-T2 fixes the binding 60 minutes after the first refusal. Four
|
||||||
|
hours later the journal reads `refused ×5, gave-up` and the DM was never
|
||||||
|
delivered. A typo in the binding hits every blocking decision at once, so
|
||||||
|
all of them give up within minutes. The only way back is to edit the
|
||||||
|
journal by hand.
|
||||||
|
|
||||||
|
Decision 72 gives the reason for five: "Five is enough to ride out a binding
|
||||||
|
typo fixed within a few hours. Fewer would give up on a fixable mistake."
|
||||||
|
The candidate follows the brief's wording (five, from the journal, one
|
||||||
|
gave-up line), but at this pace five does not do what the ruling chose it
|
||||||
|
for. The new test can't see the problem because it advances the clock 31
|
||||||
|
minutes between attempts.
|
||||||
|
|
||||||
|
A restart makes it worse. The backoff lives in memory, so after a restart
|
||||||
|
the next attempt goes at once (probe F2-T3). The unit restarts after 15 s
|
||||||
|
on a failure exit, so a host that crashes in a loop spends the count even
|
||||||
|
faster.
|
||||||
|
|
||||||
|
My round 1 note 7 on #1521 said a refused DM "retries at most every 30
|
||||||
|
min". That is the cap, not the pace, and it may be where the "few hours"
|
||||||
|
estimate came from. That's my error.
|
||||||
|
|
||||||
|
Fix:
|
||||||
|
|
||||||
|
1. A definite refusal schedules the next attempt at `BACKOFF_MAX_MS`
|
||||||
|
instead of the doubling step. Five refusals then span at least 2 hours.
|
||||||
|
2. On open, take each decision's next attempt time from its last definite
|
||||||
|
refusal's `at` plus `BACKOFF_MAX_MS`, so a restart doesn't attempt early.
|
||||||
|
The journal already holds the timestamp.
|
||||||
|
3. Test: poll every `POLL_MS` with a permanent 403. Assert no gave-up line
|
||||||
|
before 2 hours and one by 2 hours plus a poll. Restart in the middle and
|
||||||
|
assert no early send.
|
||||||
|
|
||||||
|
Sage owns the ruling. If five refusals at any pace is the intent, the
|
||||||
|
decision should say so, and B1 becomes a note. As written, the reason given
|
||||||
|
for five doesn't hold.
|
||||||
|
|
||||||
|
## R1 (round 2): give both close sends a callback
|
||||||
|
|
||||||
|
Rocko's open finding is real. `probe/sendwindow.mjs` SIGKILLs a child,
|
||||||
|
blocks the event loop until the child is dead, then sends:
|
||||||
|
|
||||||
|
```
|
||||||
|
nocb connected before send true
|
||||||
|
nocb process exit code 1 node:events: Unhandled 'error' event (EPIPE)
|
||||||
|
cb connected before send true
|
||||||
|
cb callback got EPIPE
|
||||||
|
cb process exit code 3
|
||||||
|
```
|
||||||
|
|
||||||
|
It behaved the same in 3 of 3 runs for each mode. That is a bare fork. In
|
||||||
|
`startHost` the send is followed at once by `ended(broker)`, whose
|
||||||
|
`once(broker, "exit")` also listens for 'error'. So the EPIPE rejects
|
||||||
|
`ended`, `startHost` rejects with the raw error instead of the notifier's
|
||||||
|
CliError, and `cli.mjs` rethrows a non-CliError: exit 1 with a stack trace.
|
||||||
|
Darkwing measured this through `startHost` (5 of 80 runs, comment 26872).
|
||||||
|
At :150 the refusal (exit 3, kept down by `RestartPreventExitStatus`)
|
||||||
|
becomes exit 1, which the unit restarts every 15 s. At :144 the exit code
|
||||||
|
is 1 either way, but the operator loses the notifier's message.
|
||||||
|
Since round 2 is needed for B1 anyway, fold it in:
|
||||||
|
`broker.send({ op: "close" }, () => {})` at both places. A test is
|
||||||
|
optional: the window needs the event loop blocked between the kill and the
|
||||||
|
send.
|
||||||
|
|
||||||
|
## Contested choices
|
||||||
|
|
||||||
|
- J4 checks every complete line, not only confirmed ones. Sage accepted
|
||||||
|
that. Probe J4-A2 shows that every line shape the row 39 writer produces
|
||||||
|
still opens.
|
||||||
|
- A confirmed line needs a string `messageId`: **agree.** `rest.mjs`
|
||||||
|
`createMessage` throws `unknown` on a 2xx without a string id, so the
|
||||||
|
writer always has one.
|
||||||
|
- A digest line must not carry a decision: **agree.** The writer writes
|
||||||
|
`null`.
|
||||||
|
- `gave-up` only on a dm: **agree.** Only DMs give up.
|
||||||
|
|
||||||
|
## Mutants
|
||||||
|
|
||||||
|
All 18 of Rocko's are killed under my harness as well, each by a failing
|
||||||
|
test. Of my 14, 11 are killed.
|
||||||
|
|
||||||
|
| Mutant | Result |
|
||||||
|
|---|---|
|
||||||
|
| N2, N4, N5, M28, G144, G150, F2a to F2f, J4a to J4d, E1, E2 | killed |
|
||||||
|
| X1 every refusal counts (no status check) | killed |
|
||||||
|
| X2 a 5xx refusal counts | survived; equivalent, `rest.mjs` never refuses with a 5xx |
|
||||||
|
| X3 the count is not rebuilt on open | killed |
|
||||||
|
| X4 a digest needs no `day` | killed |
|
||||||
|
| X5 `gave-up` allowed on a digest | killed |
|
||||||
|
| X6 no CliError when the directory can't be created | killed |
|
||||||
|
| X7 a digest may carry a decision | killed |
|
||||||
|
| X8 no symlink message on ELOOP | killed |
|
||||||
|
| X9 no CliError when `sent.jsonl` itself is unwritable | **survived**; test gap |
|
||||||
|
| X10 a non-confirmed `messageId` may be any type | killed |
|
||||||
|
| X11 any `kind` | killed |
|
||||||
|
| X12 gave-up lines not loaded | killed |
|
||||||
|
| X13 `giveUp` writes no line | killed |
|
||||||
|
| X14 the install message drops the `mkdir -m 0700` line | **survived**; test gap |
|
||||||
|
|
||||||
|
X9 and X14 each need a one-line test (a 0400 `sent.jsonl`, and an
|
||||||
|
assertion on the install message). They are optional.
|
||||||
|
|
||||||
|
## Notes (not blocking)
|
||||||
|
|
||||||
|
1. **`append` doesn't type-check.** Probe J4-A1: a fake Discord that
|
||||||
|
returns `messageId: 123` gets a confirmed line written, and the next open
|
||||||
|
refuses with exit 3. The real writer can't produce that today (see
|
||||||
|
above), so this is hardening only: running `badField` before the write
|
||||||
|
would turn a future regression into a send error rather than a unit that
|
||||||
|
stays down.
|
||||||
|
2. **Recovery is undocumented.** After a gave-up, the README doesn't say
|
||||||
|
what a fixed binding does: nothing, the DM is final. One line would help:
|
||||||
|
the decision stays in `mosaic inbox` and the digest.
|
||||||
|
3. **Digest refusals have no limit** (probe F2-T5: 7 sends in an hour on a
|
||||||
|
permanent 403). That matches the ruling, which limits only DMs.
|
||||||
|
4. **`deadPid`** checks once that a reaped pid is free. In theory it could
|
||||||
|
be reused before the test uses it. That is far better than the fixed
|
||||||
|
number it replaces.
|
||||||
|
5. The brief's other items match: the reader-cap exposure check, `t.after`
|
||||||
|
in "a notifier that dies", the `broker.connected` guard on both closes,
|
||||||
|
the unwritable and symlink messages, the install line, and the trackers
|
||||||
|
boot test moved (byte-identical to Sage's copy).
|
||||||
|
|
||||||
|
## Darkwing's review (comment 26872)
|
||||||
|
|
||||||
|
Darkwing also asks for changes, and we found the same two things on our
|
||||||
|
own: their R1 is my R1, their R2 is my B1. I agree with their extra
|
||||||
|
points:
|
||||||
|
|
||||||
|
- Add the callback to the `close()` sends at :184 and :188 too. They
|
||||||
|
predate this row and have the same window.
|
||||||
|
- A 401 from a bad token also counts as definite, which widens B1.
|
||||||
|
- Their notes 1 to 5 are not blocking. Note 2 is my X14. Note 3: the
|
||||||
|
`day` pattern accepts `2026-13-45`.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `gate.sh`, `mutants.sh`, `probe/probe.test.mjs`, `probe/sendwindow.mjs`
|
||||||
|
- Output:
|
||||||
|
- `r1-gate-summary.txt`
|
||||||
|
- `r1-mut-summary.txt`
|
||||||
|
- `r1-node24.txt`
|
||||||
|
- `r1-probe.txt`
|
||||||
|
- `r1-sendwindow.txt`
|
||||||
|
- `r1-manifest-mut-after.txt`
|
||||||
|
- `r1-cand-*.txt` and `r1-base-*.txt` (each suite, teed)
|
||||||
Reference in New Issue
Block a user