docs(slice1): filbert row 39 S4 round 2 review record (approve)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sequential gate; $1 = tree, $2 = out prefix
|
||||||
|
T="$1"; P="$2"; O=~/filbert-scratch/r39b/out; cd "$T"
|
||||||
|
for p in cli bus business discord; do
|
||||||
|
[ -d packages/$p/tests ] || continue
|
||||||
|
env -u NODE_TEST_CONTEXT node --test "packages/$p/tests/*.test.mjs" > "$O/$P-node-$p.txt" 2>&1; echo "$P node-$p exit $?"
|
||||||
|
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-r39b.sock timeout 900 bash "$s" > "$O/$P-suite-$n.txt" 2>&1; echo "$P suite-$n exit $? load $(cut -d" " -f1 /proc/loadavg)"
|
||||||
|
done
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Row 39 round 2 mutants: Filbert's round 1 set (M1-M34), Rocko's round 2
|
||||||
|
# set (R5-F3, from agents/rocko/work/slice1-s4/mutants-r2.sh) and N1-N6.
|
||||||
|
# One perl substitution each, restored after its run. A run counts as
|
||||||
|
# killed when any test fails or is cancelled; --test-timeout turns a hang
|
||||||
|
# (M19 waits on a readline prompt) into a cancellation.
|
||||||
|
# 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 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
|
||||||
|
run M1 $NT 's/if \(!d\.blocking \|\| journal\.sent\.has\(d\.id\)\) continue;/if (journal.sent.has(d.id)) continue;/'
|
||||||
|
run M2 $NT 's/hour >= DIGEST_HOUR/hour > DIGEST_HOUR/'
|
||||||
|
run M3 $NT 's/!journal\.days\.has\(day\) &&/true \&\&/'
|
||||||
|
run M4 $NT 's/return b !== undefined && t < b\.next;/return false;/'
|
||||||
|
run M5 $NT 's/if \(st\.uid !== process\.getuid\(\) \|\| \(st\.mode & 0o777\) !== 0o600\)/if (false)/'
|
||||||
|
run M6 $NT 's/const torn = lines\.pop\(\);/const torn = "";/'
|
||||||
|
run M7 $NT 's/export const ZONE = "America\/Chicago";/export const ZONE = "UTC";/'
|
||||||
|
run M8 $NT 's/export const dmNonce = \(id\) => `dm\$\{[^;]*;/export const dmNonce = (id) => "dmfixed";/'
|
||||||
|
run M9 $NT 's/\(dmSent\(d\.id\) \? "\[blocking, DM sent\] " : "\[blocking, DM pending\] "\)/"[blocking] "/'
|
||||||
|
run M10 $NT 's/Math\.min\(BACKOFF_MS \* 2 \*\* n, BACKOFF_MAX_MS\) \}\);/BACKOFF_MS });/'
|
||||||
|
run M11 packages/discord/src/notify.mjs 's/if \(err\.kind === "refused"\) channel = null;//'
|
||||||
|
run M12 packages/discord/src/notify.mjs 's/throw new RestOutcome\(err\.kind, `dm: \$\{err\.kind\}`/throw new RestOutcome(err.kind, err.message/'
|
||||||
|
run M13 packages/discord/src/notify.mjs 's/if \(binding\.dmRecipient === null\)[^\n]*\n//'
|
||||||
|
run M14 packages/discord/src/binding.mjs 's/if \(!users\.some\(\(u\) => u\.id === dmRecipient\)\)[^\n]*\n//'
|
||||||
|
run M15 packages/discord/src/binding.mjs 's/"tokenFile", "dmRecipient", "engine", "context"/"tokenFile", "engine", "context"/'
|
||||||
|
TR=packages/cli/src/transport.mjs
|
||||||
|
run M16 $TR 's/if \(found\.length > 0\) \{/if (false) {/'
|
||||||
|
run M17 $TR 's/"outcome-unknown": 1,/"outcome-unknown": 2,/'
|
||||||
|
run M18 $TR 's/if \(proc\.error \|\| proc\.status === null\)/if (proc.error)/'
|
||||||
|
CL=packages/cli/src/cli.mjs
|
||||||
|
run M19 $CL 's/if \(!io\.stdin\.isTTY\) throw usage/if (false) throw usage/'
|
||||||
|
run M20 $CL 's/if \(ref\.length < 8\)/if (ref.length < 1)/'
|
||||||
|
run M21 $CL 's/\|\| \(st\.mode & 0o777\) !== 0o600\)/)/'
|
||||||
|
run M22 $CL 's/if \(e\.code === "decision-closed"\)[^\n]*\n//'
|
||||||
|
run M23 $CL 's/if \(hits\.length > 1\)[^\n]*\n//'
|
||||||
|
run M24 $CL 's/if \(state\?\.live\) return state\.business;/if (state) return state.business;/'
|
||||||
|
CF=packages/cli/src/config.mjs
|
||||||
|
run M25 $CF 's/if \(named\.length > 1\)/if (named.length > 2)/'
|
||||||
|
run M26 $CF 's/if \(vars\["tracker\.project"\] !== undefined\) named\.push/named.push/'
|
||||||
|
HO=packages/cli/src/host.mjs
|
||||||
|
run M27 $HO 's/close\(1\);\n \};/close(0);\n };/'
|
||||||
|
run M28 $HO 's/if \(prior\?\.live\) throw/if (false) throw/'
|
||||||
|
run M29 $HO 's/if \(i < 0 \|\| argv\[i \+ 1\] !== "bus" \|\| argv\[i \+ 2\] !== "start"\)/if (false)/'
|
||||||
|
run M30 $HO 's/return fields\[0\] === "Z" \? null : fields\[19\];/return fields[19];/'
|
||||||
|
run M31 $HO 's/if \(ready\?\.ok !== true\) \{/if (ready?.ok === "never") {/'
|
||||||
|
run M32 packages/cli/src/format.mjs 's/if \(decision\?\.task_ref\) out\.push[^\n]*\n//'
|
||||||
|
run M33 packages/cli/src/notifier-process.mjs 's/process\.on\("SIGTERM", \(\) => stop\(0\)\);//'
|
||||||
|
run M34 packages/discord/src/rest.mjs 's/if \(typeof recipientId !== "string" \|\| !\/\^\[0-9\]\{17,20\}\$\/\.test\(recipientId\)\)/if (false)/'
|
||||||
|
run R5 $NT 's/if \(!st\.isFile\(\) \|\| st\.uid !== process\.getuid\(\) \|\| \(st\.mode & 0o777\) !== 0o600\)/if (false)/'
|
||||||
|
run R6 $NT 's/if \(end < bytes\.length\) \{/if (false) {/'
|
||||||
|
run R6b $NT 's/ftruncateSync\(fd, end\);//'
|
||||||
|
run R6c $NT 's/const name = copyTorn\(dir, bytes\.subarray\(end\), now\(\)\);/const name = "none";/'
|
||||||
|
run F1 $NT 's/if \(!ds\.isDirectory\(\) \|\| ds\.uid !== process\.getuid\(\) \|\| \(ds\.mode & 0o077\) !== 0\)/if (false)/'
|
||||||
|
run J3 $NT 's/O_RDWR \| O_APPEND \| O_CREAT \| O_NOFOLLOW/O_RDWR | O_APPEND | O_CREAT/'
|
||||||
|
run D1 $NT 's/hash23\(`\$\{business\}\\n\$\{day\}`\)/hash23(day)/'
|
||||||
|
run RACE packages/cli/src/host.mjs 's/if \(child\.exitCode !== null \|\| child\.signalCode !== null\) onDeath\(name, child\.exitCode, child\.signalCode\);\n\s*else //'
|
||||||
|
run F3 packages/cli/systemd/mosaic-bus.service.in 's/\[Unit\]\n/[Unit]\nAfter=network-online.target\n/'
|
||||||
|
# Round 2, Filbert
|
||||||
|
run N1 $NT 's/O_WRONLY \| O_CREAT \| constants\.O_EXCL \| O_NOFOLLOW/O_WRONLY | O_CREAT | O_NOFOLLOW/'
|
||||||
|
run N2 $NT 's/lstatSync, mkdirSync/lstatSync, statSync, mkdirSync/; s/const ds = lstatSync\(dir\);/const ds = statSync(dir);/'
|
||||||
|
run N3 $NT 's/const dfd = openSync\(dir, constants\.O_RDONLY\);\n try \{\n fsyncSync\(dfd\);/const dfd = openSync(dir, constants.O_RDONLY);\n try {\n 0;/'
|
||||||
|
run N4 $NT 's/openSync\(file, O_WRONLY \| O_APPEND \| O_NOFOLLOW\)/openSync(file, O_WRONLY | O_APPEND)/'
|
||||||
|
run N5 packages/cli/src/host.mjs 's/if \(child\.exitCode !== null \|\| child\.signalCode !== null\) onDeath/if (child.exitCode !== null) onDeath/'
|
||||||
|
run N6 $NT 's/ftruncateSync\(fd, end\);/ftruncateSync(fd, end > 0 ? end - 1 : 0);/'
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Filbert, row 39 r2: notify journal edge cases against the candidate.
|
||||||
|
// Usage: node probe.mjs <candidate root>
|
||||||
|
import { appendFileSync, chmodSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
const { openJournal, journalPath } = await import(process.argv[2] + "/packages/cli/src/notifier.mjs");
|
||||||
|
const scratch = join(homedir(), "filbert-scratch", "r39b", "probe", "tmp");
|
||||||
|
mkdirSync(scratch, { recursive: true });
|
||||||
|
const fresh = () => journalPath(mkdtempSync(join(scratch, "j-")), "demo");
|
||||||
|
const torn = (f) => readdirSync(dirname(f)).filter((n) => n.startsWith("torn-")).sort();
|
||||||
|
const tryOpen = (f, o) => { try { return openJournal(f, o); } catch (e) { return { error: `${e.exitCode ?? e.code}: ${e.message.replace(scratch, "<s>")}` }; } };
|
||||||
|
const rec = (d) => JSON.stringify({ at: "x", kind: "dm", decision: d, outcome: "confirmed", messageId: "1" });
|
||||||
|
|
||||||
|
// T1: a confirmed record whose newline never landed is treated as torn: copied out, dropped, so the decision is DM'd again.
|
||||||
|
{ const f = fresh(); openJournal(f).append(JSON.parse(rec("a"))); appendFileSync(f, rec("b"));
|
||||||
|
const logs = []; const j = openJournal(f, { log: (l) => logs.push(l) });
|
||||||
|
console.log("T1 sent after open", JSON.stringify([...j.sent]), "torn copies", torn(f).length, "logs", logs.length); }
|
||||||
|
// T2: the round 1 B1 sequence: torn tail, append, reopen.
|
||||||
|
{ const f = fresh(); openJournal(f).append(JSON.parse(rec("a"))); appendFileSync(f, '{"at":"x","kind":"dm","dec');
|
||||||
|
openJournal(f).append(JSON.parse(rec("b"))); const r = tryOpen(f);
|
||||||
|
console.log("T2 reopen", r.error ?? JSON.stringify([...r.sent])); }
|
||||||
|
// T3: torn tail plus a malformed middle line: refuses, file and directory untouched.
|
||||||
|
{ const f = fresh(); openJournal(f); writeFileSync(f, `${rec("a")}\nnot json\n${rec("b").slice(0, 10)}`); const before = readFileSync(f, "utf8");
|
||||||
|
console.log("T3", tryOpen(f).error, "| unchanged", readFileSync(f, "utf8") === before, "| torn copies", torn(f).length); }
|
||||||
|
// T4: torn tail with a loose file mode: refuses before any repair.
|
||||||
|
{ const f = fresh(); openJournal(f); writeFileSync(f, "{\"a"); chmodSync(f, 0o644);
|
||||||
|
console.log("T4", tryOpen(f).error, "| torn copies", torn(f).length); }
|
||||||
|
// T5: the journal directory is a symlink to a 0700 directory.
|
||||||
|
{ const root = mkdtempSync(join(scratch, "j-")); const real = join(root, "real"); mkdirSync(real, { mode: 0o700 });
|
||||||
|
mkdirSync(join(root, "notify"), { mode: 0o700 }); symlinkSync(real, join(root, "notify", "demo"));
|
||||||
|
console.log("T5", tryOpen(journalPath(root, "demo")).error ?? "accepted"); }
|
||||||
|
// T6: three crashes between copy and truncate, then a clean open: one copy per attempt, none overwritten.
|
||||||
|
{ const f = fresh(); openJournal(f); writeFileSync(f, "{\"torn"); const now = () => new Date("2026-10-09T01:00:00Z");
|
||||||
|
for (let i = 0; i < 3; i++) tryOpen(f, { now, log: () => { throw new Error("crash"); } });
|
||||||
|
const j = openJournal(f, { now }); console.log("T6 copies", JSON.stringify(torn(f)), "journal bytes", readFileSync(f).length, "sent", j.sent.size); }
|
||||||
|
// T7: a torn tail in a directory the user cannot write (0500): what refuses, and how.
|
||||||
|
{ const f = fresh(); openJournal(f); writeFileSync(f, "{\"torn"); chmodSync(dirname(f), 0o500);
|
||||||
|
console.log("T7", tryOpen(f).error ?? "opened", "| journal", JSON.stringify(readFileSync(f, "utf8"))); chmodSync(dirname(f), 0o700); }
|
||||||
|
// T8: a blank complete line (e.g. "\n\n" from a hand edit).
|
||||||
|
{ const f = fresh(); openJournal(f); writeFileSync(f, `${rec("a")}\n\n`); console.log("T8", tryOpen(f).error ?? "opened"); }
|
||||||
|
rmSync(scratch, { recursive: true, force: true });
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (217.310414ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (303.537957ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (253.286695ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (160.225541ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (162.19786ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (128.801264ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (149.430351ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (86.051671ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (157.309435ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (223.035126ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (210.412718ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (191.349274ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (222.442927ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (195.17119ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.246744ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.360273ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.628386ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (0.592553ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.278882ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.323611ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.685349ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (1.400191ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.8581ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.305429ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (260.83156ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (226.690882ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (233.511399ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (248.645504ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (41.71346ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (198.391657ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (157.488841ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (173.653461ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (37.15333ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (165.733757ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (1094.861222ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (343.625879ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (233.997111ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (150.27752ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (294.068958ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (169.324257ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (181.47066ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (174.542442ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (182.622361ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (167.596658ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (237.783601ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (307.546305ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (211.423445ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (171.415558ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (234.689651ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (188.953806ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (164.016855ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (86.356441ms)
|
||||||
|
✔ async transactions refuse before invoking their function (83.160309ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (205.097157ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (215.068339ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (127.551648ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (11.827025ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (118.23409ms)
|
||||||
|
ℹ tests 58
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 58
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2739.556368
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (1.261412ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (3.486499ms)
|
||||||
|
✔ two instances may share a definition (1.135312ms)
|
||||||
|
✔ top-level refusals (4.052468ms)
|
||||||
|
✔ arbiters and projects (5.50727ms)
|
||||||
|
✔ role instances (2.908438ms)
|
||||||
|
✔ Vikunja bots (5.357349ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (1.918244ms)
|
||||||
|
✔ credential references match the definition's services (2.41167ms)
|
||||||
|
✔ launch (7.652372ms)
|
||||||
|
✔ loadBusiness: file checks (1.760046ms)
|
||||||
|
✔ loadBusiness: not a regular file (43.811309ms)
|
||||||
|
✔ loading writes nothing (1.147463ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (3.088735ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (0.806055ms)
|
||||||
|
✔ usage errors exit 4 (281.842061ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (65.70548ms)
|
||||||
|
✔ validate: project files (343.187247ms)
|
||||||
|
✔ validate: missing files and a broken system config (246.116384ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (68.667591ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (67.922015ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (214.225914ms)
|
||||||
|
✔ resolve: prints one instance's record (208.183465ms)
|
||||||
|
✔ resolve: refusals (390.711309ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (2.300609ms)
|
||||||
|
✔ check: a good file has no problems (0.624398ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.309092ms)
|
||||||
|
✔ check: file problems (0.677698ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.619586ms)
|
||||||
|
✔ check: dates and environment references (0.321745ms)
|
||||||
|
✔ path and load (1.70873ms)
|
||||||
|
✔ refusals (1.06147ms)
|
||||||
|
✔ systemVars flattens the validated config (1.602672ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (4.096363ms)
|
||||||
|
✔ limits narrow the definition and never widen it (1.855891ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (3.74717ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (1.445106ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (0.934994ms)
|
||||||
|
✔ classify (1.225729ms)
|
||||||
|
✔ the record carries what the broker and launcher need (0.879546ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (5.684839ms)
|
||||||
|
✔ refusals (1.786089ms)
|
||||||
|
✔ the four shipped version 2 roles load (2.972336ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.142959ms)
|
||||||
|
✔ shipped authority follows the note's table (0.654999ms)
|
||||||
|
✔ version 1 files keep loading with no authority (0.911567ms)
|
||||||
|
✔ the conductor policy isn't a role (0.193819ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.337323ms)
|
||||||
|
✔ version 2 refusals (1.328539ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (2.030509ms)
|
||||||
|
✔ credentials: Gitea scopes (0.828626ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.097888ms)
|
||||||
|
✔ credentials: services (0.512348ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (0.606346ms)
|
||||||
|
✔ every key names known layers and a merge rule (0.66841ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (0.557532ms)
|
||||||
|
✔ types (1.552146ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.269319ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.340672ms)
|
||||||
|
✔ merge doesn't change its inputs (0.136302ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 1946.375781
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.311357ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.701935ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.666129ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.466461ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.530003ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (8.97202ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (9.876476ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.614364ms)
|
||||||
|
✔ authorize: open channel, listed user (1.918309ms)
|
||||||
|
✔ authorize: wrong guild (0.209469ms)
|
||||||
|
✔ authorize: no guild (DM) (0.183666ms)
|
||||||
|
✔ authorize: unlisted channel (0.244643ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.196908ms)
|
||||||
|
✔ authorize: thread of listed parent (0.202889ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.191015ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.153491ms)
|
||||||
|
✔ authorize: unlisted user (0.204601ms)
|
||||||
|
✔ authorize: no author (0.916465ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.173926ms)
|
||||||
|
✔ authorize: system author (0.120911ms)
|
||||||
|
✔ authorize: the bot itself (0.10735ms)
|
||||||
|
✔ authorize: webhook (0.100761ms)
|
||||||
|
✔ authorize: mention channel without mention (0.140699ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.151843ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.167888ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.089486ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.089341ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.084556ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.05975ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.069707ms)
|
||||||
|
✔ authorize: not an object (0.061778ms)
|
||||||
|
✔ authorize: no id (0.07487ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.075356ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.081014ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.486988ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.204973ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (2.928165ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.414856ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.496834ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.373232ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.044495ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (1.679612ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.276414ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (102.613686ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.50081ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (354.541628ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (214.315306ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (121.851384ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.735489ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.184618ms)
|
||||||
|
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (18.267254ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.980222ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.475552ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.498543ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.464203ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.505835ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.203656ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.334043ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.96087ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.052453ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.357704ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.2268ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.648472ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.041924ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.401109ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.260933ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.889649ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.694598ms)
|
||||||
|
✔ 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 (1.741159ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.260447ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.2361ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.330372ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (2.893545ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.642891ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.830227ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.927868ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.4971ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.540519ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.506593ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.924662ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.701325ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.471719ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (67.931376ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (45.990579ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (35.765858ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (353.640991ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.414967ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (103.36315ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (233.842944ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (131.995471ms)
|
||||||
|
✔ 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.759436ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (616.153458ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.412333ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.447298ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.505852ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (25.976253ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (48.518719ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.727944ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.902931ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.711452ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.386186ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.977284ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.593665ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.626696ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.657153ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (45.88357ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (87.721754ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.302868ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (88.384999ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (90.045981ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (103.560019ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (206.388678ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (65.616181ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (92.830705ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (213.591577ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (837.248736ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.220185ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (4.518775ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.422894ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.582692ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (63.8715ms)
|
||||||
|
✔ 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 (344.239416ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.336098ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.362061ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.76214ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.224352ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (129.362888ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (63.552354ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.51231ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (53.144558ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (47.151395ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (32.2825ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (82.540159ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (679.325926ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.657912ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.281283ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (3.152613ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.761836ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.390986ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (2.644296ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.538722ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.010351ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (40.502527ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (9.130128ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (5.5993ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.048084ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.504142ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.120153ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1009.602815ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.504453ms)
|
||||||
|
✔ 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.184188ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.273923ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.202957ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.283008ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.339522ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.333603ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.855722ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.752295ms)
|
||||||
|
✔ tools: listing and search caps hold (21.069267ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.870034ms)
|
||||||
|
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (8.328982ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.063217ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.163075ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.921007ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.552488ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.46398ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.339588ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.329664ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1033.562992ms)
|
||||||
|
✔ 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.6957ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.268031ms)
|
||||||
|
✔ 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.797272ms)
|
||||||
|
✔ 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.368338ms)
|
||||||
|
ℹ tests 173
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 173
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2671.315407
|
||||||
@@ -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 'b9b6cf008a8d7083676fc4a2760ae50fd43ad9b0'.
|
||||||
|
|
||||||
|
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 'b9b6cf008a8d7083676fc4a2760ae50fd43ad9b0'.
|
||||||
|
|
||||||
|
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,68 @@
|
|||||||
|
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/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/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 173)
|
||||||
|
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: 64 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: 192975 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/r39b/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,66 @@
|
|||||||
|
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (139.654375ms)
|
||||||
|
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (247.063501ms)
|
||||||
|
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (228.702372ms)
|
||||||
|
✔ launch events require a human CLI capability; generic emit cannot forge authority events (152.589158ms)
|
||||||
|
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (154.294352ms)
|
||||||
|
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (128.295573ms)
|
||||||
|
✔ task action subjects and linked decision trail are complete and ordered (137.15036ms)
|
||||||
|
✔ launch binding is durable and reconnecting requires the identical trusted record (64.285079ms)
|
||||||
|
✔ business isolation includes inherited object names and cross-business message references (146.396237ms)
|
||||||
|
✔ authority never transfers between action, run, target, unresolved or replaced role holder (243.938983ms)
|
||||||
|
✔ task projection uses schema current view, skipping earlier and equal-start polls (94.170395ms)
|
||||||
|
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (161.232705ms)
|
||||||
|
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (101.449267ms)
|
||||||
|
✔ both arbiters require human resolution when their cross-role route is themselves (201.868218ms)
|
||||||
|
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.792956ms)
|
||||||
|
✔ only validated broker references load; returned data and exceptions cannot expose a known token (3.145636ms)
|
||||||
|
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (1.852726ms)
|
||||||
|
✔ expiry refuses use and env references never become client data (1.151507ms)
|
||||||
|
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.274286ms)
|
||||||
|
✔ opaque tokens shorter than 16 characters refuse before use (0.249982ms)
|
||||||
|
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (1.518729ms)
|
||||||
|
✔ process reader gets own kernel identity without exposing environment values (1.029208ms)
|
||||||
|
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.603923ms)
|
||||||
|
✔ real pid 1 remains inspectable when its environment is protected (0.304814ms)
|
||||||
|
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (170.40665ms)
|
||||||
|
✔ missing and foreign-business citations refuse and roll back message and grant (167.569254ms)
|
||||||
|
✔ cross-role sends still need a matching resolved decision and consume it once (202.66731ms)
|
||||||
|
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (170.952763ms)
|
||||||
|
✔ startup token refusal returns safe code without value or partial listening broker (36.821787ms)
|
||||||
|
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (165.196727ms)
|
||||||
|
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (170.727787ms)
|
||||||
|
✔ trusted host registers later launches; socket clients never have a registration verb (164.592257ms)
|
||||||
|
✔ runtime excludes declared project roots even when host supplies no repoRoots (32.806422ms)
|
||||||
|
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (153.089197ms)
|
||||||
|
✔ v3b prototype refusals, views and append-only mutations (947.370703ms)
|
||||||
|
✔ gated approval authorizes once, survives store reopen, and fresh approval works (241.796595ms)
|
||||||
|
✔ another run cannot consume an approval; a failed check leaves it usable (198.279698ms)
|
||||||
|
✔ two scheduled callers have exactly one grant and one consumed refusal (141.759733ms)
|
||||||
|
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (271.282029ms)
|
||||||
|
✔ class drift gated to cross-role refuses before consumption (168.101142ms)
|
||||||
|
✔ class drift cross-role to gated refuses before consumption (161.738213ms)
|
||||||
|
✔ class drift gated to within-role refuses before consumption (157.073495ms)
|
||||||
|
✔ class drift cross-role to within-role refuses before consumption (206.009333ms)
|
||||||
|
✔ class drift within-role to gated refuses before consumption (144.380123ms)
|
||||||
|
✔ class drift within-role to cross-role refuses before consumption (121.658912ms)
|
||||||
|
✔ message.send consumes approval and prevents a later send or authorize (166.58839ms)
|
||||||
|
✔ role.revoke consumes approval and prevents a later revoke or authorize (221.729859ms)
|
||||||
|
✔ creates private WAL store and excludes a second writer until explicit close (115.826599ms)
|
||||||
|
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (156.703344ms)
|
||||||
|
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (170.059617ms)
|
||||||
|
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (152.580868ms)
|
||||||
|
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (92.676541ms)
|
||||||
|
✔ async transactions refuse before invoking their function (77.571318ms)
|
||||||
|
✔ socket capability stamps launch identity; shared views use wire, no SQL client (136.661151ms)
|
||||||
|
✔ two wire claims serialize; a lost reply never automatically retries (177.631582ms)
|
||||||
|
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (106.21344ms)
|
||||||
|
✔ client preserves UTF-8 when a response divides a multibyte character (12.320555ms)
|
||||||
|
✔ committed mutation followed by dropped reply reports unknown and is never retried (120.72807ms)
|
||||||
|
ℹ tests 58
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 58
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2281.14019
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
✔ config directory and file path follow MOSAIC_CONFIG (1.341014ms)
|
||||||
|
✔ the fixture business validates and comes back frozen (3.783753ms)
|
||||||
|
✔ two instances may share a definition (1.283933ms)
|
||||||
|
✔ top-level refusals (3.513758ms)
|
||||||
|
✔ arbiters and projects (6.980478ms)
|
||||||
|
✔ role instances (2.481886ms)
|
||||||
|
✔ Vikunja bots (5.977217ms)
|
||||||
|
✔ a role without Vikunja takes no tracker block (2.187817ms)
|
||||||
|
✔ credential references match the definition's services (2.491705ms)
|
||||||
|
✔ launch (7.223763ms)
|
||||||
|
✔ loadBusiness: file checks (1.762953ms)
|
||||||
|
✔ loadBusiness: not a regular file (41.927398ms)
|
||||||
|
✔ loading writes nothing (0.980735ms)
|
||||||
|
✔ names that are Object.prototype properties don't count as declared (2.715734ms)
|
||||||
|
✔ the shipped example refuses as written and validates once filled in (0.550139ms)
|
||||||
|
✔ usage errors exit 4 (284.740895ms)
|
||||||
|
✔ validate: a good business exits 0 and prints instance digests (63.480045ms)
|
||||||
|
✔ validate: project files (309.702262ms)
|
||||||
|
✔ validate: missing files and a broken system config (244.883943ms)
|
||||||
|
✔ validate: credential reference problems exit 2 and name each one (78.619778ms)
|
||||||
|
✔ validate: a token file inside the repository is refused (83.544263ms)
|
||||||
|
✔ validate: role definitions come from MOSAIC_ROLES_DIR (347.298703ms)
|
||||||
|
✔ resolve: prints one instance's record (220.944754ms)
|
||||||
|
✔ resolve: refusals (568.395168ms)
|
||||||
|
✔ parse: exactly one of file or env, plus the service's date (2.488417ms)
|
||||||
|
✔ check: a good file has no problems (0.727746ms)
|
||||||
|
✔ check never opens the file: a write-only token passes (0.321555ms)
|
||||||
|
✔ check: file problems (0.837081ms)
|
||||||
|
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.729554ms)
|
||||||
|
✔ check: dates and environment references (0.373624ms)
|
||||||
|
✔ path and load (2.09583ms)
|
||||||
|
✔ refusals (1.025402ms)
|
||||||
|
✔ systemVars flattens the validated config (1.734096ms)
|
||||||
|
✔ precedence: system, business, project, project role, agent (4.324111ms)
|
||||||
|
✔ limits narrow the definition and never widen it (2.066702ms)
|
||||||
|
✔ role.launch stays within-role only for the instance the launch block names (4.116684ms)
|
||||||
|
✔ limits.authority without role.launch leaves the launcher with no launch block (1.570418ms)
|
||||||
|
✔ limits.authority narrows cross-role actions too (1.247099ms)
|
||||||
|
✔ classify (1.04595ms)
|
||||||
|
✔ the record carries what the broker and launcher need (0.868636ms)
|
||||||
|
✔ digest: key order doesn't matter, any value change does (5.570897ms)
|
||||||
|
✔ refusals (1.953933ms)
|
||||||
|
✔ the four shipped version 2 roles load (3.020071ms)
|
||||||
|
✔ shipped role scopes match addendum B section 2 and the SR runbook (0.911031ms)
|
||||||
|
✔ shipped authority follows the note's table (0.503269ms)
|
||||||
|
✔ version 1 files keep loading with no authority (0.976922ms)
|
||||||
|
✔ the conductor policy isn't a role (0.20077ms)
|
||||||
|
✔ a missing role file is exit 4, a symbolic link too (0.346796ms)
|
||||||
|
✔ version 2 refusals (1.510228ms)
|
||||||
|
✔ authority: closed vocabulary, no gated-only action, no overlap (1.956643ms)
|
||||||
|
✔ credentials: Gitea scopes (0.842503ms)
|
||||||
|
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (0.965281ms)
|
||||||
|
✔ credentials: services (0.507288ms)
|
||||||
|
✔ contract: a non-empty regular Markdown file beside the role file (0.60658ms)
|
||||||
|
✔ every key names known layers and a merge rule (1.074622ms)
|
||||||
|
✔ unknown keys and wrong layers refuse (0.713932ms)
|
||||||
|
✔ types (1.651585ms)
|
||||||
|
✔ merge: defaults, then the most specific layer wins (0.235419ms)
|
||||||
|
✔ merge: limits only narrow, and provenance lists each source (0.392174ms)
|
||||||
|
✔ merge doesn't change its inputs (0.172929ms)
|
||||||
|
ℹ tests 60
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 60
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2270.726837
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (100.078936ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (107.319875ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (99.730956ms)
|
||||||
|
✔ decide prints a declining choice as declining (90.510381ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (112.00473ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (108.740493ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (77.352582ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (45.635253ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (45.464533ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (45.159304ms)
|
||||||
|
✔ agents and tasks print through the broker (53.456446ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.27951ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (37.49377ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (36.732668ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (36.58984ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (28.492158ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (29.24871ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (55.884577ms)
|
||||||
|
✔ empty views say so (1.044953ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (1.380369ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.224637ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (859.449566ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (187.04901ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (173.229976ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (22.622042ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (202.886448ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (210.010048ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (84.912019ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (650.965231ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (27.10127ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (13.969441ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (94.732038ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (109.219033ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.214105ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (87.262675ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (91.765134ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (90.620084ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (0.714447ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (97.129267ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (28.777778ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (29.548192ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (14.661147ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.862905ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.5764ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.315122ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (111.13999ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (33.566934ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2140.027835ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.491405ms)
|
||||||
|
ℹ tests 49
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 49
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2517.736153
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.542746ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.934868ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (1.475863ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.538308ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (25.448134ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (11.685966ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (10.841998ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (5.170841ms)
|
||||||
|
✔ authorize: open channel, listed user (22.287105ms)
|
||||||
|
✔ authorize: wrong guild (0.267614ms)
|
||||||
|
✔ authorize: no guild (DM) (0.191727ms)
|
||||||
|
✔ authorize: unlisted channel (0.222855ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.316141ms)
|
||||||
|
✔ authorize: thread of listed parent (0.250805ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.208692ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.149837ms)
|
||||||
|
✔ authorize: unlisted user (1.531999ms)
|
||||||
|
✔ authorize: no author (0.293545ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.132914ms)
|
||||||
|
✔ authorize: system author (2.617179ms)
|
||||||
|
✔ authorize: the bot itself (0.129547ms)
|
||||||
|
✔ authorize: webhook (0.690146ms)
|
||||||
|
✔ authorize: mention channel without mention (0.243579ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.238357ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.236778ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.081781ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.081841ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.076594ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.081193ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.089329ms)
|
||||||
|
✔ authorize: not an object (0.052593ms)
|
||||||
|
✔ authorize: no id (0.052998ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.05717ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.049367ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.411752ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.121684ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (5.790531ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.767833ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.530678ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.434008ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.160588ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.286517ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (1.69439ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.417494ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (104.652492ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.813887ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (366.266935ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (207.600874ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (120.080515ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.565436ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.016626ms)
|
||||||
|
✔ 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 (24.754313ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (23.517154ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (4.521681ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (3.510393ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.836254ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (4.523989ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.772732ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (6.500024ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.770107ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (2.071734ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.380346ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.375624ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.859983ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (10.699491ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.121998ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.571623ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.913785ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.765934ms)
|
||||||
|
✔ 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 (1.938226ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.278103ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (9.799634ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.248465ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.792951ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.49622ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.897878ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.224316ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.331116ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.57512ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.340266ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.711663ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.660918ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.597104ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (66.981725ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (46.315019ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (38.85986ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (362.520383ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (232.404059ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (105.101303ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (227.882056ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (124.917471ms)
|
||||||
|
✔ 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.301179ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.943458ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.407506ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.63402ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (435.256752ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (28.489293ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.291897ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.731491ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.78848ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.694244ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.387033ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.046497ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.620206ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.539526ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (106.5672ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (66.381881ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (89.732409ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.306919ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (81.547628ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (93.304949ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (114.704235ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (201.165005ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (60.465398ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (83.600115ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (218.04961ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (888.492779ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.649702ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (5.603631ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.088287ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.778693ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (60.984081ms)
|
||||||
|
✔ 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 (342.062302ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.42599ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.443703ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.79832ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (49.089317ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (131.551284ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (69.87592ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.708582ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.667109ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.177148ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.87754ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (50.94603ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (43.95032ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (50.103263ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (72.273143ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (677.205774ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.518193ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.114429ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.643462ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.020396ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.044026ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.027816ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (3.325777ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.78746ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.620258ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.582174ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.508143ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.497698ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.755965ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.068823ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.314254ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.527762ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.434897ms)
|
||||||
|
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (5.922125ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.195623ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.197563ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.298838ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.450242ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.223885ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.4028ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.476365ms)
|
||||||
|
✔ tools: listing and search caps hold (17.774841ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (1.049228ms)
|
||||||
|
✔ 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 (9.337057ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.339143ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.20303ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.917216ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.149135ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.826738ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.567972ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.262031ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1029.97115ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (6.108577ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.337669ms)
|
||||||
|
✔ 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.857954ms)
|
||||||
|
✔ 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.362929ms)
|
||||||
|
ℹ tests 178
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 178
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2744.162363
|
||||||
@@ -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 'b9b6cf008a8d7083676fc4a2760ae50fd43ad9b0'.
|
||||||
|
|
||||||
|
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 'b9b6cf008a8d7083676fc4a2760ae50fd43ad9b0'.
|
||||||
|
|
||||||
|
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: 141359 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/r39b/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,25 @@
|
|||||||
|
cand node-cli exit 0
|
||||||
|
cand node-bus exit 0
|
||||||
|
cand node-business exit 0
|
||||||
|
cand node-discord exit 0
|
||||||
|
cand suite-auth exit 0 load 3.88
|
||||||
|
cand suite-conductor exit 0 load 4.13
|
||||||
|
cand suite-config exit 0 load 4.13
|
||||||
|
cand suite-discord exit 0 load 4.12
|
||||||
|
cand suite-extension-package exit 0 load 3.87
|
||||||
|
cand suite-foundation exit 0 load 3.46
|
||||||
|
cand suite-queue exit 0 load 3.97
|
||||||
|
cand suite-release exit 0 load 3.97
|
||||||
|
cand suite-task exit 1 load 3.97
|
||||||
|
base node-bus exit 0
|
||||||
|
base node-business exit 0
|
||||||
|
base node-discord exit 0
|
||||||
|
base suite-auth exit 0 load 4.29
|
||||||
|
base suite-conductor exit 0 load 5.31
|
||||||
|
base suite-config exit 0 load 5.31
|
||||||
|
base suite-discord exit 0 load 5.47
|
||||||
|
base suite-extension-package exit 0 load 5.47
|
||||||
|
base suite-foundation exit 0 load 4.24
|
||||||
|
base suite-queue exit 0 load 4.43
|
||||||
|
base suite-release exit 0 load 4.43
|
||||||
|
base suite-task exit 1 load 4.72
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (851.881094ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (171.408869ms)
|
||||||
|
mosaic-notify: notify: poll failed: ENOENT: no such file or directory, open '/tmp/mosaic-cli-RnYDTZ/data/notify/acme/sent.jsonl'
|
||||||
|
✖ a notifier that refuses stops the broker and the host refuses with exit 3 (163.293325ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (22.529171ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (202.458765ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (219.699321ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (89.542554ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (685.795533ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (40.826807ms)
|
||||||
|
ℹ tests 9
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 8
|
||||||
|
ℹ fail 1
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2532.392133
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at packages/cli/tests/host.test.mjs:117:1
|
||||||
|
✖ a notifier that refuses stops the broker and the host refuses with exit 3 (163.293325ms)
|
||||||
|
AssertionError [ERR_ASSERTION]: Missing expected rejection.
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||||
|
at async TestContext.<anonymous> (file:///home/jwoltje/filbert-scratch/r39b/mut/packages/cli/tests/host.test.mjs:125:3)
|
||||||
|
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: undefined,
|
||||||
|
operator: 'rejects',
|
||||||
|
diff: 'simple'
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (87.216667ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (72.518856ms)
|
||||||
|
✖ decide refuses without a terminal or --yes, on an unknown option and on a short reference (60001.70155ms)
|
||||||
|
✔ decide prints a declining choice as declining (71.997826ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (54.943141ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (50.941742ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (51.85766ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (45.937668ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (48.960227ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (51.712577ms)
|
||||||
|
✔ agents and tasks print through the broker (49.316379ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.177967ms)
|
||||||
|
ℹ tests 12
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 11
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 1
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 60677.507287
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at packages/cli/tests/cli.test.mjs:66:1
|
||||||
|
✖ decide refuses without a terminal or --yes, on an unknown option and on a short reference (60001.70155ms)
|
||||||
|
'test timed out after 60000ms'
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (128.195384ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (173.03349ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (101.725873ms)
|
||||||
|
✔ decide prints a declining choice as declining (211.031203ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (168.845654ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (197.827882ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (142.434566ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (105.022882ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (114.596595ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (66.603429ms)
|
||||||
|
✔ agents and tasks print through the broker (68.059725ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.285612ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (92.375276ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (61.819103ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (60.559355ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (49.117414ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (53.852451ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (87.694477ms)
|
||||||
|
✔ empty views say so (1.285418ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (1.478037ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.28882ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1033.632014ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (262.045607ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (203.472241ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (22.806465ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (203.474754ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (245.182997ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (101.641085ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (687.392239ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (27.544558ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (26.513988ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (103.232685ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (166.594618ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.542796ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (101.736767ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (176.495893ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (175.331443ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (1.349032ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (176.712746ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (56.520073ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (49.840375ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (22.119255ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.003867ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.994243ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.486197ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (110.970108ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (54.522158ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2267.743264ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.466679ms)
|
||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.446995ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.777098ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.701757ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.514843ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.376227ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (6.783828ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (11.817459ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.623937ms)
|
||||||
|
✔ authorize: open channel, listed user (1.991359ms)
|
||||||
|
✔ authorize: wrong guild (0.234897ms)
|
||||||
|
✔ authorize: no guild (DM) (0.176042ms)
|
||||||
|
✔ authorize: unlisted channel (0.199873ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.184158ms)
|
||||||
|
✔ authorize: thread of listed parent (0.238941ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.155529ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.183767ms)
|
||||||
|
✔ authorize: unlisted user (0.190022ms)
|
||||||
|
✔ authorize: no author (0.365207ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.173026ms)
|
||||||
|
✔ authorize: system author (0.118875ms)
|
||||||
|
✔ authorize: the bot itself (0.099871ms)
|
||||||
|
✔ authorize: webhook (0.095675ms)
|
||||||
|
✔ authorize: mention channel without mention (0.149411ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.17239ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.113569ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.096703ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.094882ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.098952ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.105221ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.086125ms)
|
||||||
|
✔ authorize: not an object (0.080474ms)
|
||||||
|
✔ authorize: no id (0.08ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.083916ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.072872ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.48684ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.164021ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (3.221593ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.664308ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.489484ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.530283ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.331597ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.395098ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (2.156108ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (3.713437ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (115.304298ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.29538ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (434.958653ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (238.946386ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (152.871089ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (3.135857ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (2.122184ms)
|
||||||
|
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (18.934291ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (30.088217ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (3.499336ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (2.520585ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.451321ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.229017ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.681902ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.973696ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.294851ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.799863ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (45.065449ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.457106ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.686495ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.778305ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.148577ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.217326ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.351708ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.873163ms)
|
||||||
|
✔ 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.348064ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.501793ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.052151ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.393233ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.212187ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.823545ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.82393ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.13179ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.298018ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.744131ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.733223ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.762688ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.944535ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.465626ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (62.822814ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (61.145295ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (65.381089ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (351.693041ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (252.49447ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (104.961156ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.952688ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (136.060149ms)
|
||||||
|
✔ 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.56674ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.013047ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.50905ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.982612ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (438.371393ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (25.024093ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.214839ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.80776ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.836378ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.562911ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.397066ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.091203ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.585441ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.509514ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (81.630624ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (55.182558ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (104.588538ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.386886ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (129.509564ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (129.242538ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (128.941211ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (270.059409ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (108.38127ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (120.832286ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (235.297232ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (870.316574ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.461445ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (6.077463ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.154073ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.958004ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (68.497107ms)
|
||||||
|
✔ 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 (414.276972ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.568485ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.622695ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.24818ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (65.895538ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (136.975315ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (62.784536ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.47959ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.202273ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.347778ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.088161ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (46.846093ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (46.862321ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (36.118273ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (85.679261ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (786.96409ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.469546ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.499215ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.663236ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.10301ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.019735ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.327066ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (2.605257ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.225081ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.048754ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.04122ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.465965ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.034413ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.868421ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.667357ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.697976ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.422397ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.574031ms)
|
||||||
|
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (7.422913ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.467746ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.250436ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.738467ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.390017ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.716188ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.057023ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.659219ms)
|
||||||
|
✔ tools: listing and search caps hold (12.247961ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.942743ms)
|
||||||
|
✔ 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.486088ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.284782ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.243556ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.044765ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.66857ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.687135ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.829048ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.407129ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1029.680966ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (5.749177ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.345888ms)
|
||||||
|
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (3.21551ms)
|
||||||
|
✔ 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.689858ms)
|
||||||
|
ℹ tests 227
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 227
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2941.501687
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (127.753251ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (129.025855ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (96.418066ms)
|
||||||
|
✔ decide prints a declining choice as declining (100.853056ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (96.778988ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (105.734474ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (93.012442ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (53.206865ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (62.921536ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (56.550824ms)
|
||||||
|
✔ agents and tasks print through the broker (57.582994ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.307361ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (64.984295ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (65.53152ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (62.872752ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (53.003211ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (50.542782ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (74.0262ms)
|
||||||
|
✔ empty views say so (2.944357ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (3.005385ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.30078ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (974.684957ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (214.521088ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (203.644138ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (25.221184ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (203.41222ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (215.480332ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (93.644985ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (651.972183ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (26.333963ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (30.582528ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (126.420982ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (123.200882ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.488688ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (100.613569ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (102.035032ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (86.894435ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (1.01765ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (102.020379ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (25.701484ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (38.403667ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (20.953522ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.164213ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.676866ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.322469ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (111.200287ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (82.057379ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2253.399727ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.42075ms)
|
||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.628277ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.75015ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.789884ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.528425ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.730053ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (8.531053ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.368222ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.490163ms)
|
||||||
|
✔ authorize: open channel, listed user (2.163248ms)
|
||||||
|
✔ authorize: wrong guild (0.413078ms)
|
||||||
|
✔ authorize: no guild (DM) (0.246179ms)
|
||||||
|
✔ authorize: unlisted channel (0.226812ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.216533ms)
|
||||||
|
✔ authorize: thread of listed parent (0.244115ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.18617ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.212158ms)
|
||||||
|
✔ authorize: unlisted user (0.216673ms)
|
||||||
|
✔ authorize: no author (1.07892ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.556837ms)
|
||||||
|
✔ authorize: system author (0.132411ms)
|
||||||
|
✔ authorize: the bot itself (0.124679ms)
|
||||||
|
✔ authorize: webhook (0.092331ms)
|
||||||
|
✔ authorize: mention channel without mention (0.367416ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.15319ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.094163ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.106898ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (0.07215ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.107247ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.076093ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.081567ms)
|
||||||
|
✔ authorize: not an object (0.078452ms)
|
||||||
|
✔ authorize: no id (0.087671ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.088343ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.103414ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.510951ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.151798ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (2.811979ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.534343ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.489052ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.332179ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.730779ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.479475ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (1.561869ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.360705ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (118.459641ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.138223ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (390.500298ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (225.842844ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (127.056523ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.86736ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.263217ms)
|
||||||
|
✔ 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 (19.600503ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (22.072709ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.633239ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.375273ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.379997ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.479605ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (35.877674ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (6.498832ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.019322ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.114521ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.243936ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.749582ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.897904ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.729896ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.503019ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.841041ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.776015ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.802936ms)
|
||||||
|
✔ 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 (6.148514ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.668272ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.084101ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.3259ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.035844ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.65906ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.859541ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.287212ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.651253ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.766928ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.413465ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.742759ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.827857ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.491479ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (70.86549ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (69.620553ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (51.782501ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (359.437182ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (238.14282ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (104.388246ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (227.259601ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.831582ms)
|
||||||
|
✔ 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.412083ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.408499ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.270054ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.489266ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.920283ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (26.809844ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.871482ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (4.042801ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.783841ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.518696ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.356799ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.956958ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.57353ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.404589ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (89.033224ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (63.115487ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (104.076686ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.349054ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (92.960511ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (110.702274ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (126.24749ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (201.410346ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (79.932738ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (93.41013ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (240.864545ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (836.648473ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.93703ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (5.34184ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.049106ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.501652ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (61.604453ms)
|
||||||
|
✔ 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 (388.69257ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.431042ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.413009ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.859556ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (40.072031ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (131.561811ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (69.87073ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.616648ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.579462ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (4.713692ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (4.738148ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (48.62245ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (47.091302ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (33.514263ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (92.247189ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (725.566027ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.80118ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.155356ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (0.665527ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.148698ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.866994ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (4.907299ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (2.885344ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.415831ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.676002ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.652602ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.608155ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.134591ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.890858ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (2.733054ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.599123ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.42976ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.570767ms)
|
||||||
|
✔ 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.650093ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.427899ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.226813ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.807204ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.286692ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.667324ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.102094ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.099343ms)
|
||||||
|
✔ tools: listing and search caps hold (11.508786ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.881486ms)
|
||||||
|
✔ 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.835094ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.19701ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.125946ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.987562ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.708844ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.627306ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.544969ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.000021ms)
|
||||||
|
✔ 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.136539ms)
|
||||||
|
✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (5.463547ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.314305ms)
|
||||||
|
✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (3.573597ms)
|
||||||
|
✔ web: the tool set enables the web tools only with a web key, counts them in the budget, and records url, status and hits (3.249626ms)
|
||||||
|
ℹ tests 227
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 227
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2765.910563
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
M1 killed (fail 1, cancelled 0)
|
||||||
|
M2 killed (fail 1, cancelled 0)
|
||||||
|
M3 killed (fail 2, cancelled 0)
|
||||||
|
M4 killed (fail 1, cancelled 0)
|
||||||
|
M5 NOT-APPLIED packages/cli/src/notifier.mjs
|
||||||
|
M6 NOT-APPLIED packages/cli/src/notifier.mjs
|
||||||
|
M7 killed (fail 5, cancelled 0)
|
||||||
|
M8 killed (fail 1, cancelled 0)
|
||||||
|
M9 killed (fail 1, cancelled 0)
|
||||||
|
M10 killed (fail 1, cancelled 0)
|
||||||
|
M11 killed (fail 1, cancelled 0)
|
||||||
|
M12 killed (fail 1, cancelled 0)
|
||||||
|
M13 killed (fail 2, cancelled 0)
|
||||||
|
M14 killed (fail 1, cancelled 0)
|
||||||
|
M15 killed (fail 2, cancelled 0)
|
||||||
|
M16 killed (fail 2, cancelled 0)
|
||||||
|
M17 killed (fail 1, cancelled 0)
|
||||||
|
M18 SURVIVED
|
||||||
|
M19 killed (fail 0, cancelled 1)
|
||||||
|
M20 killed (fail 1, cancelled 0)
|
||||||
|
M21 killed (fail 1, cancelled 0)
|
||||||
|
M22 killed (fail 1, cancelled 0)
|
||||||
|
M23 killed (fail 1, cancelled 0)
|
||||||
|
M24 killed (fail 1, cancelled 0)
|
||||||
|
M25 killed (fail 1, cancelled 0)
|
||||||
|
M26 killed (fail 1, cancelled 0)
|
||||||
|
M27 NOT-APPLIED packages/cli/src/host.mjs
|
||||||
|
M28 SURVIVED
|
||||||
|
M29 killed (fail 1, cancelled 0)
|
||||||
|
M30 killed (fail 1, cancelled 0)
|
||||||
|
M31 killed (fail 1, cancelled 0)
|
||||||
|
M32 killed (fail 2, cancelled 0)
|
||||||
|
M33 SURVIVED
|
||||||
|
M34 killed (fail 1, cancelled 0)
|
||||||
|
R5 killed (fail 1, cancelled 0)
|
||||||
|
R6 killed (fail 3, cancelled 0)
|
||||||
|
R6b killed (fail 3, cancelled 0)
|
||||||
|
R6c killed (fail 3, cancelled 0)
|
||||||
|
F1 killed (fail 1, cancelled 0)
|
||||||
|
J3 killed (fail 1, cancelled 0)
|
||||||
|
D1 killed (fail 1, cancelled 0)
|
||||||
|
RACE killed (fail 1, cancelled 0)
|
||||||
|
F3 killed (fail 1, cancelled 0)
|
||||||
|
N1 killed (fail 1, cancelled 0)
|
||||||
|
N2 SURVIVED
|
||||||
|
N3 SURVIVED
|
||||||
|
N4 SURVIVED
|
||||||
|
N5 SURVIVED
|
||||||
|
N6 killed (fail 2, cancelled 0)
|
||||||
|
manifest exit 0
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
v24.21.0
|
||||||
|
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (133.148875ms)
|
||||||
|
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (144.284714ms)
|
||||||
|
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (105.577091ms)
|
||||||
|
✔ decide prints a declining choice as declining (149.14756ms)
|
||||||
|
✔ an unknown outcome is reported once and never resent (98.973182ms)
|
||||||
|
✔ a decision closed before the answer arrives exits 2 and points at its trail (116.155887ms)
|
||||||
|
✔ a prefix that matches two open decisions exits 2 and resolves neither (86.731111ms)
|
||||||
|
✔ without --business a command uses the live host's business, and a stale host.json is not a host (60.683256ms)
|
||||||
|
✔ every human command refuses inside an agent run before it touches the bus (59.372134ms)
|
||||||
|
✔ usage errors exit 4; no business and no host is a usage error (59.391836ms)
|
||||||
|
✔ agents and tasks print through the broker (52.619851ms)
|
||||||
|
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.306544ms)
|
||||||
|
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (70.290138ms)
|
||||||
|
✔ trackers come from the tracker.* variables of the one project that names a tracker project (45.218875ms)
|
||||||
|
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (73.190968ms)
|
||||||
|
✔ two projects that each name a tracker project refuse, since the boot shape holds one (50.949454ms)
|
||||||
|
✔ a business without tracker.baseUrl gets no trackers entry (49.646488ms)
|
||||||
|
✔ an unknown business and a broken system config refuse with exit 3 (73.975271ms)
|
||||||
|
✔ empty views say so (1.324689ms)
|
||||||
|
✔ the trail keeps the broker's order and names a decision's task without its rows (2.356319ms)
|
||||||
|
✔ tasks print the tracker fields the snapshot carries (0.221546ms)
|
||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1034.927582ms)
|
||||||
|
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (186.210396ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (174.291356ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (22.270706ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (202.662447ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (201.680155ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (90.929576ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (654.588193ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (37.869352ms)
|
||||||
|
✔ zoned uses the IANA zone across DST (55.713246ms)
|
||||||
|
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (135.192226ms)
|
||||||
|
✔ two blocking decisions get two DMs with different nonces (126.131514ms)
|
||||||
|
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.357865ms)
|
||||||
|
✔ a failed DM is journaled, backs off, and is retried until it lands (97.327268ms)
|
||||||
|
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (145.621758ms)
|
||||||
|
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (97.606994ms)
|
||||||
|
✔ an inbox read failure is logged and the next poll retries (1.289062ms)
|
||||||
|
✔ no Discord id reaches the journal or the log (108.828729ms)
|
||||||
|
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (19.13901ms)
|
||||||
|
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (39.211452ms)
|
||||||
|
✔ the journal: a whole file that is one torn line truncates to empty (24.705091ms)
|
||||||
|
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.989135ms)
|
||||||
|
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.748042ms)
|
||||||
|
✔ digest content stays within Discord's 2000 characters (0.320981ms)
|
||||||
|
✔ runLoop never overlaps ticks and stops after the one in flight (111.21029ms)
|
||||||
|
✔ the transport writes {business, verb, args} to the child and reads its JSON (58.297057ms)
|
||||||
|
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2219.381688ms)
|
||||||
|
✔ busExit and refuseInsideAgent (0.347476ms)
|
||||||
|
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.867876ms)
|
||||||
|
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.930998ms)
|
||||||
|
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.622318ms)
|
||||||
|
✔ approvals: a button approves only on its own request message with the matching custom id (0.745477ms)
|
||||||
|
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (22.317701ms)
|
||||||
|
✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (8.212755ms)
|
||||||
|
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.007654ms)
|
||||||
|
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.234751ms)
|
||||||
|
✔ authorize: open channel, listed user (1.91511ms)
|
||||||
|
✔ authorize: wrong guild (0.197809ms)
|
||||||
|
✔ authorize: no guild (DM) (0.329677ms)
|
||||||
|
✔ authorize: unlisted channel (0.237793ms)
|
||||||
|
✔ authorize: unknown channel, no info (0.170099ms)
|
||||||
|
✔ authorize: thread of listed parent (0.207444ms)
|
||||||
|
✔ authorize: thread of unlisted parent (0.174588ms)
|
||||||
|
✔ authorize: text channel that is not a thread and not listed (0.146914ms)
|
||||||
|
✔ authorize: unlisted user (2.273145ms)
|
||||||
|
✔ authorize: no author (0.330478ms)
|
||||||
|
✔ authorize: bot author (listed id, bot flag) (0.15287ms)
|
||||||
|
✔ authorize: system author (0.134099ms)
|
||||||
|
✔ authorize: the bot itself (0.094939ms)
|
||||||
|
✔ authorize: webhook (0.101119ms)
|
||||||
|
✔ authorize: mention channel without mention (0.156474ms)
|
||||||
|
✔ authorize: mention channel with bot mention (0.153772ms)
|
||||||
|
✔ authorize: mention channel with @everyone only (0.108938ms)
|
||||||
|
✔ authorize: mention channel mentioning someone else (0.093841ms)
|
||||||
|
✔ authorize: mention channel, content says @bot but mentions empty (3.283104ms)
|
||||||
|
✔ authorize: private thread under mention channel, mentioned (0.142484ms)
|
||||||
|
✔ authorize: private thread under mention channel, not mentioned (0.081361ms)
|
||||||
|
✔ authorize: thread in another guild per channel info (0.072228ms)
|
||||||
|
✔ authorize: not an object (0.369775ms)
|
||||||
|
✔ authorize: no id (0.096019ms)
|
||||||
|
✔ authorize: oversize content is accepted and flagged (0.080047ms)
|
||||||
|
✔ authorize: exactly the limit is not oversize (0.064125ms)
|
||||||
|
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.537813ms)
|
||||||
|
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.166175ms)
|
||||||
|
✔ binding: a complete binding validates and is frozen (2.683269ms)
|
||||||
|
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.858271ms)
|
||||||
|
✔ binding: empty allowlists refuse (0.698964ms)
|
||||||
|
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (2.117376ms)
|
||||||
|
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.08131ms)
|
||||||
|
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.115265ms)
|
||||||
|
✔ binding: file must be 0600, regular, not a symlink (1.68097ms)
|
||||||
|
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (3.174229ms)
|
||||||
|
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (117.285167ms)
|
||||||
|
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.909175ms)
|
||||||
|
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (377.041478ms)
|
||||||
|
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (216.295324ms)
|
||||||
|
✔ cli: run refuses when STOP is present, before any network use (115.048351ms)
|
||||||
|
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.467178ms)
|
||||||
|
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.09963ms)
|
||||||
|
✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (21.316467ms)
|
||||||
|
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (33.272973ms)
|
||||||
|
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.688757ms)
|
||||||
|
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.275857ms)
|
||||||
|
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.39992ms)
|
||||||
|
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.560595ms)
|
||||||
|
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (36.07126ms)
|
||||||
|
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.736815ms)
|
||||||
|
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (3.359329ms)
|
||||||
|
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (3.802886ms)
|
||||||
|
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (46.352161ms)
|
||||||
|
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.3766ms)
|
||||||
|
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.460856ms)
|
||||||
|
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.778733ms)
|
||||||
|
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.070541ms)
|
||||||
|
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.080508ms)
|
||||||
|
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.213655ms)
|
||||||
|
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.63942ms)
|
||||||
|
✔ 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 (1.918608ms)
|
||||||
|
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.440224ms)
|
||||||
|
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.739841ms)
|
||||||
|
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.712085ms)
|
||||||
|
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.645544ms)
|
||||||
|
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.595148ms)
|
||||||
|
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.816121ms)
|
||||||
|
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (3.90803ms)
|
||||||
|
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.357139ms)
|
||||||
|
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (2.524516ms)
|
||||||
|
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.16571ms)
|
||||||
|
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.69284ms)
|
||||||
|
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.867992ms)
|
||||||
|
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.456893ms)
|
||||||
|
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (53.731313ms)
|
||||||
|
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (55.72278ms)
|
||||||
|
✔ engine: one prompt, one turn, text and usage come back (51.932374ms)
|
||||||
|
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (345.279587ms)
|
||||||
|
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (244.214588ms)
|
||||||
|
✔ engine: timeout sends abort and fails only that turn; the process stays (105.437654ms)
|
||||||
|
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (224.117287ms)
|
||||||
|
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.738318ms)
|
||||||
|
✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (212.59562ms)
|
||||||
|
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.157126ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.351493ms)
|
||||||
|
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.556148ms)
|
||||||
|
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.018428ms)
|
||||||
|
✔ engine: a malformed JSONL line fails the turn, not the process (25.43066ms)
|
||||||
|
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.951295ms)
|
||||||
|
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.69792ms)
|
||||||
|
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.869035ms)
|
||||||
|
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.533233ms)
|
||||||
|
✔ gateway: op 9 resumable resumes (0.383641ms)
|
||||||
|
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.340036ms)
|
||||||
|
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.636877ms)
|
||||||
|
✔ gateway: close() is final and unparseable frames are ignored (0.462875ms)
|
||||||
|
✔ git: config validation is strict, needs write: true, a work tree and a private token file (160.392307ms)
|
||||||
|
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (59.185612ms)
|
||||||
|
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (99.969922ms)
|
||||||
|
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.307647ms)
|
||||||
|
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (80.090709ms)
|
||||||
|
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (126.0413ms)
|
||||||
|
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (109.954892ms)
|
||||||
|
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (167.839313ms)
|
||||||
|
✔ git: push pushes the named branch only and reports up to date (53.632654ms)
|
||||||
|
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (67.062539ms)
|
||||||
|
✔ git: the credential helper answers get over https from a private file and nothing else (175.014557ms)
|
||||||
|
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (259.931632ms)
|
||||||
|
✔ lock: the claim is exclusive; a second start against a live owner refuses (4.113238ms)
|
||||||
|
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (5.474769ms)
|
||||||
|
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.085455ms)
|
||||||
|
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (16.807399ms)
|
||||||
|
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (65.916029ms)
|
||||||
|
✔ 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 (344.449758ms)
|
||||||
|
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.348967ms)
|
||||||
|
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (0.431166ms)
|
||||||
|
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.868604ms)
|
||||||
|
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (44.707965ms)
|
||||||
|
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (130.97353ms)
|
||||||
|
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (63.553437ms)
|
||||||
|
✔ notices: a kind is recorded per UTC day and found again (0.634182ms)
|
||||||
|
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.771808ms)
|
||||||
|
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.633108ms)
|
||||||
|
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.727567ms)
|
||||||
|
✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (43.414226ms)
|
||||||
|
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (36.289292ms)
|
||||||
|
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.93181ms)
|
||||||
|
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (78.616995ms)
|
||||||
|
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (654.649528ms)
|
||||||
|
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.318177ms)
|
||||||
|
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.7292ms)
|
||||||
|
✔ rest: content and nonce limits are enforced locally; typing never throws (1.019023ms)
|
||||||
|
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.822671ms)
|
||||||
|
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (2.16405ms)
|
||||||
|
✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.461151ms)
|
||||||
|
✔ setspark config: reaches the tools config and the binding as a fixed key (3.264288ms)
|
||||||
|
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.646026ms)
|
||||||
|
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.251922ms)
|
||||||
|
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (33.26389ms)
|
||||||
|
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.908174ms)
|
||||||
|
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.971595ms)
|
||||||
|
✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.757372ms)
|
||||||
|
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (6.206062ms)
|
||||||
|
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (11.202128ms)
|
||||||
|
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.46248ms)
|
||||||
|
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.431636ms)
|
||||||
|
✔ setspark verbs: writes carry the turn's key and the asserted requester, reads carry no key, and the api key never appears in text or details (5.826749ms)
|
||||||
|
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (2.37378ms)
|
||||||
|
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.201864ms)
|
||||||
|
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.484639ms)
|
||||||
|
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.284492ms)
|
||||||
|
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.842823ms)
|
||||||
|
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (9.336853ms)
|
||||||
|
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (2.946598ms)
|
||||||
|
✔ tools: listing and search caps hold (15.272918ms)
|
||||||
|
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.876088ms)
|
||||||
|
✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (14.495331ms)
|
||||||
|
✔ tools: an unreadable file under the root is skipped by search and refused by read (1.811072ms)
|
||||||
|
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.434261ms)
|
||||||
|
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.796861ms)
|
||||||
|
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.933489ms)
|
||||||
|
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (3.509719ms)
|
||||||
|
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.647114ms)
|
||||||
|
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.47267ms)
|
||||||
|
✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1039.582548ms)
|
||||||
|
✔ 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.443289ms)
|
||||||
|
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.308404ms)
|
||||||
|
✔ 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.850135ms)
|
||||||
|
✔ 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.433448ms)
|
||||||
|
ℹ tests 227
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 227
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 0
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 2755.78809
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
T1 sent after open ["a"] torn copies 1 logs 2
|
||||||
|
T2 reopen ["a","b"]
|
||||||
|
T3 3: notify journal line 2 is malformed: <s>/j-OP2V6r/notify/demo/sent.jsonl | unchanged true | torn copies 0
|
||||||
|
T4 3: notify journal must be a regular file, mode 0600, owned by this user: <s>/j-ohiVsv/notify/demo/sent.jsonl | torn copies 0
|
||||||
|
T5 3: notify journal directory must be mode 0700 and owned by this user: <s>/j-h6fq3N/notify/demo
|
||||||
|
T6 copies ["torn-20261009T010000000Z-1.bin","torn-20261009T010000000Z-2.bin","torn-20261009T010000000Z-3.bin","torn-20261009T010000000Z.bin"] journal bytes 0 sent 0
|
||||||
|
T7 EACCES: EACCES: permission denied, open '<s>/j-famMXt/notify/demo/torn-20261009T010503790Z.bin' | journal "{\"torn"
|
||||||
|
T8 3: notify journal line 2 is malformed: <s>/j-Pv0zPl/notify/demo/sent.jsonl
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
connected false exitCode 0
|
||||||
|
send returned without throwing
|
||||||
|
process exit code 1
|
||||||
|
node:events:505
|
||||||
|
throw er; // Unhandled 'error' event
|
||||||
|
^
|
||||||
|
|
||||||
|
Error [ERR_IPC_CHANNEL_CLOSED]: Channel closed
|
||||||
|
at target.send (node:internal/child_process:778:16)
|
||||||
|
at file:///home/jwoltje/filbert-scratch/r39b/probe/sendclosed.mjs:10:9
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||||
|
Emitted 'error' event on ChildProcess instance at:
|
||||||
|
at node:internal/child_process:782:35
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:85:11) {
|
||||||
|
code: 'ERR_IPC_CHANNEL_CLOSED'
|
||||||
|
}
|
||||||
|
|
||||||
|
Node.js v26.8.1
|
||||||
|
shell exit 1
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (873.048242ms)
|
||||||
|
✖ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (60005.00757ms)
|
||||||
|
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (185.620796ms)
|
||||||
|
✔ watchChildren reports a child that died before it was called, and one that dies later (20.748664ms)
|
||||||
|
✔ bus stop refuses to signal a live pid that is not a bus host (202.751381ms)
|
||||||
|
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (257.772528ms)
|
||||||
|
✔ bus start refuses with exit 3 without a notifier config (105.611141ms)
|
||||||
|
✔ bus start runs until bus stop; status reports it while it runs (673.656974ms)
|
||||||
|
✔ bus-service.sh renders the unit and installs it into a given directory (27.783457ms)
|
||||||
|
|
||||||
|
Interrupted while running:
|
||||||
|
|
||||||
|
⚠ packages/cli/tests/host.test.mjs (packages/cli/tests/host.test.mjs:1:1)
|
||||||
|
✖ packages/cli/tests/host.test.mjs (239968.123988ms)
|
||||||
|
ℹ tests 10
|
||||||
|
ℹ suites 0
|
||||||
|
ℹ pass 8
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 2
|
||||||
|
ℹ skipped 0
|
||||||
|
ℹ todo 0
|
||||||
|
ℹ duration_ms 239973.857677
|
||||||
|
|
||||||
|
✖ failing tests:
|
||||||
|
|
||||||
|
test at packages/cli/tests/host.test.mjs:102:1
|
||||||
|
✖ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (60005.00757ms)
|
||||||
|
'test timed out after 60000ms'
|
||||||
|
|
||||||
|
test at packages/cli/tests/host.test.mjs:1:1
|
||||||
|
✖ packages/cli/tests/host.test.mjs (239968.123988ms)
|
||||||
|
'Promise resolution is still pending but the event loop has already resolved'
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
# Slice 1 S4, row 39, round 2 review (Filbert)
|
||||||
|
|
||||||
|
Issue #1521, request comment 26854, queue rev 191, pushed at `26bd829c`.
|
||||||
|
Packet: `agents/rocko/work/slice1-s4/` (`packet-manifest.sha256`
|
||||||
|
`fef8a758d0c75c380294591a59c865c9ecc8c8e634b0e11b012a3dd481c86ec4`,
|
||||||
|
44 OK). Candidate: manifest sha256
|
||||||
|
`e858504e54d5b06581ec8b7168b82cd4895491c980aae1c62bd8a8be5e4f2f17`,
|
||||||
|
29 files, over `b9b6cf00` with `build.patch` (sha256
|
||||||
|
`b52f7d6800538cdaf9df29300ad37672d00552e7086f496606722df3967e88f8`).
|
||||||
|
Scope: lead decision 71. Round 1: `review-r1.md` here.
|
||||||
|
|
||||||
|
Verdict: **approve.** B1 is fixed the way decision 71 lays it out, and
|
||||||
|
F1, R2, D1, the startup race, J3 and F3 are fixed and tested. So are
|
||||||
|
the round 1 test gaps M22 to M24, M26 and M29. F2 and J4 stay
|
||||||
|
follow-ups, as decision 71 says. The notes below don't block.
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
- Detached worktrees at `b9b6cf00` under `~/filbert-scratch/r39b/`: base,
|
||||||
|
candidate, and a third candidate copy for mutants. In both candidate
|
||||||
|
trees I ran `git apply build.patch` and then `sha256sum -c`: 29 OK.
|
||||||
|
After the mutant run the mutant tree checks clean again.
|
||||||
|
- No code differs between `b9b6cf00` and `26bd829c` outside `agents/`
|
||||||
|
and `docs/`.
|
||||||
|
- `gate-r2.sh` runs the suites one at a time in both trees, tees each
|
||||||
|
output and logs the load average. As in round 1, `DOCKER_HOST` points
|
||||||
|
at a socket that doesn't exist, so no Docker case runs. I touched no
|
||||||
|
Docker network.
|
||||||
|
- `probe-r2.mjs` exercises `openJournal` directly (T1 to T8).
|
||||||
|
- `sendclosed.mjs` checks what `ChildProcess.send` does on a closed
|
||||||
|
channel.
|
||||||
|
- `mutants-r2.sh` runs my round 1 mutants M1 to M34, Rocko's round 2 set
|
||||||
|
(R5 to F3, from `mutants-r2.sh` in the packet), and N1 to N6, which
|
||||||
|
target the round 2 code. Each run is the cli and discord node tests,
|
||||||
|
227 tests, under `--test-timeout=90000` and an outer `timeout 900`. A
|
||||||
|
run counts as killed on any failure, any cancellation, or the outer
|
||||||
|
timeout. My round 1 harness counted only failures, which is why M19
|
||||||
|
showed SURVIVED in Rocko's run of it.
|
||||||
|
- Node 24 run: `node:24` with no network, the tree mounted read-only, and
|
||||||
|
the host uid.
|
||||||
|
|
||||||
|
## Suites
|
||||||
|
|
||||||
|
| Suite | Candidate | Base |
|
||||||
|
|---|---|---|
|
||||||
|
| node cli (Node 26.8.1) | 49/49 | n/a |
|
||||||
|
| node cli + discord (Node 24.21.0) | 227/227 | n/a |
|
||||||
|
| node bus | 58/58 | 58/58 |
|
||||||
|
| node business | 60/60 | 60/60 |
|
||||||
|
| node discord | 178/178 | 173/173 |
|
||||||
|
| test-auth | 15/15 | 15/15 |
|
||||||
|
| test-conductor | 17/17 | 17/17 |
|
||||||
|
| test-config | 24/24 | 24/24 |
|
||||||
|
| test-discord | 66/66 | 64/64 |
|
||||||
|
| test-extension-package | 18/18 | 18/18 |
|
||||||
|
| test-foundation | 44/44 | 44/44 |
|
||||||
|
| test-queue | 27/27 | 27/27 |
|
||||||
|
| test-release | 4/4, Docker cases skipped | 4/4, same |
|
||||||
|
| test-task | 26 pass, 2 fail | 26 pass, 2 fail |
|
||||||
|
|
||||||
|
Base and candidate fail the same two test-task cases, "user recall run
|
||||||
|
succeeds" and "recalled user name", as in round 1 and row 38. That
|
||||||
|
matches Rocko's 26/2. I saw no 429 and no address-pool error.
|
||||||
|
|
||||||
|
## Sage's three questions
|
||||||
|
|
||||||
|
### 1. M19: confirmed, it never passes
|
||||||
|
|
||||||
|
M19 removes decide's no-TTY check. The test's stdin is a PassThrough
|
||||||
|
with `isTTY` false that never ends, so readline waits forever. Run alone
|
||||||
|
with `--test-timeout=60000` (`r2-m19-timeout.txt`):
|
||||||
|
|
||||||
|
```
|
||||||
|
ℹ pass 11
|
||||||
|
ℹ fail 0
|
||||||
|
ℹ cancelled 1
|
||||||
|
✖ decide refuses without a terminal or --yes, on an unknown option and on a short reference (60001.70155ms)
|
||||||
|
'test timed out after 60000ms'
|
||||||
|
```
|
||||||
|
|
||||||
|
The test is cancelled, never passed. My round 1 harness logged
|
||||||
|
SURVIVED because it read only the `fail` count. In the round 2 harness
|
||||||
|
M19 is killed (fail 0, cancelled 1). Rocko's reading is right.
|
||||||
|
|
||||||
|
### 2. M13's host: closed on every path
|
||||||
|
|
||||||
|
M13 drops the `dmRecipient === null` refusal in `notify.mjs`, so the
|
||||||
|
refusal test's `startHost` resolves a running host. Rocko's hook:
|
||||||
|
|
||||||
|
```js
|
||||||
|
t.after(async () => (await started.catch(() => null))?.close(0));
|
||||||
|
```
|
||||||
|
|
||||||
|
- **The refusal holds** (the normal case). `started` rejects, the hook
|
||||||
|
gets null and does nothing. Nothing is left to close: `startHost`
|
||||||
|
stopped the notifier and the broker before it threw.
|
||||||
|
- **The refusal regresses** (M13). `started` resolves and the hook
|
||||||
|
closes the host.
|
||||||
|
- **A different rejection.** Same as the first path: every throw in
|
||||||
|
`startHost` after a fork ends the child first.
|
||||||
|
|
||||||
|
Under M13 the test fails (fail 1), host.test ends in about 3 s, and no
|
||||||
|
process is left over (`r2-m13-host.txt`).
|
||||||
|
|
||||||
|
One gap of the same kind, in a different test. Under a mutant that
|
||||||
|
never wires `watchChildren` (W0, `watchChildren({}, …)`), "a notifier
|
||||||
|
that dies takes the host down with exit 1" waits on `host.done`
|
||||||
|
forever. That test has no `t.after`, so the broker stays up and the file
|
||||||
|
ran until my 240 s outer timeout (`r2-w0-host.txt`). The test still
|
||||||
|
fails, so the mutant is caught, but slowly. `t.after(() => host.close(0))`
|
||||||
|
would fix it. This is not blocking.
|
||||||
|
|
||||||
|
### 3. Parse first, then repair: that's the right order
|
||||||
|
|
||||||
|
`openJournal` checks the directory and the file, parses every complete
|
||||||
|
line, and only then copies and truncates a torn tail. Decision 71 fixes
|
||||||
|
the two repair steps and the exit-3 refusal, but not their order. This
|
||||||
|
order is the better one:
|
||||||
|
|
||||||
|
- A refusal changes nothing on disk. The human who fixes the bad line
|
||||||
|
sees the file as it was, torn tail included.
|
||||||
|
- The next open after the fix repairs the tail as usual.
|
||||||
|
- Repairing first would write a torn copy and truncate a journal that is
|
||||||
|
about to be refused anyway. A human would then be looking at a changed
|
||||||
|
file.
|
||||||
|
|
||||||
|
Probe T3 (a bad middle line plus a torn tail): exit 3 "notify journal
|
||||||
|
line 2 is malformed", file unchanged, no torn copy. Probe T4 (mode 0644)
|
||||||
|
also refuses before any repair.
|
||||||
|
|
||||||
|
## Decision 71, item by item
|
||||||
|
|
||||||
|
- **B1, torn tail.** `copyTorn` writes the torn bytes to
|
||||||
|
`torn-<UTC stamp>.bin`: `O_EXCL`, 0600, the file fsynced, then the
|
||||||
|
directory. A name that exists gets `-1`, `-2`, and so on. Then
|
||||||
|
`ftruncateSync` to the last newline and `fsyncSync`, and each step is
|
||||||
|
logged. Probes:
|
||||||
|
- T2 is the round 1 B1 sequence: torn tail, append, reopen. It now
|
||||||
|
reopens with `["a","b"]`.
|
||||||
|
- T6 replays a crash between the steps three times. That gives four
|
||||||
|
copies, none overwritten, and an empty journal.
|
||||||
|
- Mutants R6, R6b, R6c, N1 (no `O_EXCL`) and N6 (truncate one byte
|
||||||
|
short) are killed.
|
||||||
|
- **F1.** An existing directory with any group or other bit refuses with
|
||||||
|
exit 3. Mutant F1 is killed.
|
||||||
|
- **R2.** A test asserts different DM nonces for two decisions. Round
|
||||||
|
1's M8 (constant nonce), which survived, is now killed.
|
||||||
|
- **D1.** `digestNonce(business, day)` is `"dg"` + 23 hex characters of
|
||||||
|
sha256 of business and day. It stays within Discord's 25-character
|
||||||
|
limit. Mutant D1 is killed.
|
||||||
|
- **Startup race.** `watchChildren` checks `exitCode` and `signalCode`
|
||||||
|
before it attaches `once("exit")`. It runs after `queue` exists.
|
||||||
|
Mutant RACE is killed.
|
||||||
|
- **J3.** The journal opens with `O_NOFOLLOW`, `ELOOP` refuses with exit
|
||||||
|
3, and `fstat` checks the opened file. `append` reopens with
|
||||||
|
`O_NOFOLLOW` and no `O_CREAT`. Mutant J3 is killed.
|
||||||
|
- **F3.** `network-online.target` is gone from the unit. Mutant F3 is
|
||||||
|
killed.
|
||||||
|
- **M22 to M24, M26, M29.** Each now has a test and is killed, one
|
||||||
|
failure each.
|
||||||
|
|
||||||
|
## Notes (not blocking)
|
||||||
|
|
||||||
|
1. **Unguarded `broker.send` in `startHost`'s notifier-failure path**
|
||||||
|
(`host.mjs:144` and `:150`). `close()` checks `broker.connected`, but
|
||||||
|
these two lines don't. `sendclosed.mjs` shows what happens if the
|
||||||
|
broker is already gone:
|
||||||
|
- `send` returns.
|
||||||
|
- An unhandled `ERR_IPC_CHANNEL_CLOSED` follows on the next tick.
|
||||||
|
- The process exits 1, even with `exitCode` set to 3.
|
||||||
|
|
||||||
|
So a broker that dies while the notifier starts turns a refusal (exit
|
||||||
|
3, the unit stays down) into exit 1 (the unit restarts). That is a
|
||||||
|
narrow window, and a restart is not wrong there. `if
|
||||||
|
(broker.connected)` on both lines would match `close()`.
|
||||||
|
`bindLaunch` has the same exposure, which is S6's to settle.
|
||||||
|
2. **The "notifier that dies" test has no `t.after`** (see question 2).
|
||||||
|
3. **M28 in BUILD.md.** Rocko's table lists M28 as killed. In my run it
|
||||||
|
survived three times out of three: the combined run plus two reruns,
|
||||||
|
227/227 pass each (`r2-mut-M28-rerun1.txt`, `r2-mut-M28-rerun2.txt`).
|
||||||
|
No test covers the "a bus host already runs" refusal. Rocko's single
|
||||||
|
failure was probably a flaky test. Round 1 rated M28 near-equivalent,
|
||||||
|
because `writer.lock` refuses a second broker, and I still do. Only
|
||||||
|
the packet's record is wrong.
|
||||||
|
4. **N2 survives.** It swaps the directory's `lstatSync` for `statSync`,
|
||||||
|
so a symlinked notify directory is accepted. Probe T5 shows the
|
||||||
|
candidate refuses one. No test does, though.
|
||||||
|
5. **N4 survives.** It drops `O_NOFOLLOW` from `append`, so a symlink
|
||||||
|
swapped in after the open would be followed. Like J3 in round 1, this
|
||||||
|
sits behind the 0700 directory and records the boundary only.
|
||||||
|
6. **N5 survives.** It makes `watchChildren`'s early check ignore
|
||||||
|
`signalCode`. The early-death test exits with code 7, and no test
|
||||||
|
kills a child with a signal before the watch.
|
||||||
|
7. **N3 survives.** It drops the directory fsync in `copyTorn`. That
|
||||||
|
can't be tested in-process. Equivalent for the suite.
|
||||||
|
8. **T1, a confirmed line that lost only its newline.** It is treated as
|
||||||
|
torn and dropped, so the decision is DM'd again with the same nonce.
|
||||||
|
Decision 71 accepts this: a duplicate costs less than a miss, and the
|
||||||
|
nonce folds a retry inside Discord's dedupe window.
|
||||||
|
9. **T5 wording.** A symlinked directory refuses with "must be mode 0700
|
||||||
|
and owned by this user". That is true, but it doesn't say the
|
||||||
|
directory is a link.
|
||||||
|
10. **T7, a read-only journal directory.** `copyTorn` throws a raw
|
||||||
|
`EACCES`, not a `CliError`. The notifier still refuses to start, the
|
||||||
|
host exits 3, and the journal is untouched. Fail-closed, with a less
|
||||||
|
clear message.
|
||||||
|
11. **Live run.** Rocko documents that an existing notify directory
|
||||||
|
looser than 0700 needs `chmod 700` before the first start. Round 1
|
||||||
|
note 6 (`dmRecipient` is a FIXED_KEY) still applies to the binding
|
||||||
|
edit.
|
||||||
|
12. F2 and J4 stay follow-ups (decision 71). Round 1 notes 5 and 8 still
|
||||||
|
apply.
|
||||||
|
|
||||||
|
## Mutants
|
||||||
|
|
||||||
|
| Mutant | Result |
|
||||||
|
|---|---|
|
||||||
|
| M1 to M4, M7 to M17, M20 to M26, M29 to M32, M34 | killed |
|
||||||
|
| M8 constant DM nonce | killed (R2 test; survived in round 1) |
|
||||||
|
| M19 decide drops the no-TTY check | killed (cancelled 1; see question 1) |
|
||||||
|
| M22, M23, M24, M26, M29 | killed (new tests; survived in round 1) |
|
||||||
|
| M5, M6, M27 | not applied; round 2 rewrote those lines |
|
||||||
|
| M18, M33 | survived; equivalent and near-equivalent, as in round 1 |
|
||||||
|
| M28 | survived three times; near-equivalent, as in round 1 (note 3) |
|
||||||
|
| R5, R6, R6b, R6c, F1, J3, D1, RACE, F3 (Rocko) | killed |
|
||||||
|
| N1 `copyTorn` without `O_EXCL` | killed |
|
||||||
|
| N2 directory `statSync`, not `lstatSync` | **survived** (note 4) |
|
||||||
|
| N3 no directory fsync in `copyTorn` | survived; not testable in-process (note 7) |
|
||||||
|
| N4 `append` without `O_NOFOLLOW` | **survived** (note 5) |
|
||||||
|
| N5 early check ignores `signalCode` | **survived** (note 6) |
|
||||||
|
| N6 truncate to `end - 1` | killed |
|
||||||
|
|
||||||
|
46 applied: 39 killed, 7 survived.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `mutants-r2.sh`, `gate-r2.sh`, `probe-r2.mjs`, `sendclosed.mjs` (the
|
||||||
|
round 1 scripts stay as they were)
|
||||||
|
- Output:
|
||||||
|
- `r2-probe.txt`
|
||||||
|
- `r2-sendclosed.txt`
|
||||||
|
- `r2-mut-summary.txt`
|
||||||
|
- `r2-m19-timeout.txt`, `r2-m13-host.txt`, `r2-w0-host.txt`
|
||||||
|
- `r2-mut-M28-rerun1.txt`, `r2-mut-M28-rerun2.txt`
|
||||||
|
- `r2-node24.txt`
|
||||||
|
- `r2-gate-summary.txt`
|
||||||
|
- `r2-cand-*.txt` and `r2-base-*.txt` (each suite, teed)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Filbert, row 39 r2: what ChildProcess.send() does on a child that has exited, as
|
||||||
|
// startHost's notifier-failure path would call broker.send({op: "close"}) after the broker died.
|
||||||
|
import { fork } from "node:child_process";
|
||||||
|
import { once } from "node:events";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
if (process.argv[2] === "child") process.exit(0);
|
||||||
|
const c = fork(fileURLToPath(import.meta.url), ["child"], { stdio: ["ignore", "inherit", "inherit", "ipc"] });
|
||||||
|
await once(c, "exit");
|
||||||
|
console.log("connected", c.connected, "exitCode", c.exitCode);
|
||||||
|
try { c.send({ op: "close" }); console.log("send returned without throwing"); } catch (e) { console.log("send threw", e.code); }
|
||||||
|
process.on("exit", (code) => console.log("process exit code", code));
|
||||||
|
process.exitCode = 3; // what the CLI sets for a refusal
|
||||||
Reference in New Issue
Block a user