diff --git a/agents/rocko/work/s4-follow-up/BUILD.md b/agents/rocko/work/s4-follow-up/BUILD.md new file mode 100644 index 00000000..60db4577 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/BUILD.md @@ -0,0 +1,207 @@ +# Row 45 (#1527): S4 follow-up, candidate packet + +Author: Rocko. Reviewers: Filbert and Darkwing. Brief: +`docs/plans/2026-10-09_s4-follow-up-and-cohort.md`, section "S4 follow-up". +Rulings: lead decision 72. Base: `521597bb` (`base.txt`). This packet is +uncommitted. There are no commits, pushes or Gitea calls, and no token or +private binding was read. + +## Files (`files.txt`, 8) + +| File | Change | +|---|---| +| `packages/cli/src/notifier.mjs` | F2 refusal limit, J4 type check, directory and file error messages | +| `packages/cli/src/host.mjs` | `broker.connected` guards on both `close` sends (old :144 and :150) | +| `packages/cli/README.md` | digest mark, definite-refusal bullet, type-check list, refusal wording | +| `scripts/bus-service.sh` | install message gains the `mkdir -m 0700 -p` line | +| `packages/cli/tests/notifier.test.mjs` | F2, J4, N2, N4 and error-path tests | +| `packages/cli/tests/host.test.mjs` | reader-cap exposure, M28, N5, both broker-death guards, `t.after` in "a notifier that dies" | +| `packages/cli/tests/trackers-boot.test.mjs` | new: Sage's trackers boot test, byte-identical copy | +| `packages/discord/tests/journal.test.mjs` | `deadPid()` replaces the fixed pid `2 ** 22 - 7` | + +`build.patch`: `git diff --cached --binary 521597bb` over those files, +459/−44. +It applies cleanly to 521597bb (see the gate tree's `out/manifest-check-tree.txt`). + +## What changed + +### F2: the refusal limit (decision 72) + +- `REFUSAL_LIMIT = 5`, exported. A definite refusal is a `dm` journal line + with outcome `refused` and an integer status from 400 to 499 other than + 429. A 429, an `unknown` outcome and a refusal without a status never + count. Unknown outcomes keep retrying at the 30-minute cap, with no limit. +- The count comes from the journal: `openJournal` keeps a `refusals` map + and a `gaveUp` set and updates both on every append. A restart rebuilds + them from the file. +- The fifth definite refusal appends one line, + `{kind:"dm", decision, outcome:"gave-up", messageId:null}`, and logs one + line: `notify: dm refused 5 times; gave up, not retried`. It logs no + retry line and drops the backoff entry. +- A crash between the fifth refusal and its gave-up line: the next tick sees + the count at the limit, appends the gave-up line and sends nothing. +- `tick` skips a decision that already has a gave-up line. The digest marks + it `[blocking, DM refused, not retried]`. The mark order is sent, then + gave up, then pending. + +### J4: the journal type check + +The brief says confirmed lines. I type-check **every** complete line on open, +because a bad `refused` or `gave-up` line now feeds the F2 count as well. A +bad line refuses with a CliError, exit 3: +`notify journal line N is malformed (): `. `` is `json` +or the first field that fails: + +- `record`: not a plain object. +- `at`: not an ISO string that round-trips through `toISOString()`. +- `kind`: not `dm` or `digest`. +- `decision`: for a dm, a non-empty string; for a digest, null or absent. +- `day`: required and `YYYY-MM-DD` for a digest; optional for a dm, but + well-formed if present. +- `outcome`: one of confirmed, refused, unknown or gave-up. gave-up is + allowed only on a dm. +- `messageId`: a string on `confirmed`; null or a string otherwise. +- `status`: an integer when present. + +Choices a reviewer may contest: a digest with a decision is refused, +gave-up is dm-only, and `confirmed` requires a string messageId. The test +table also proves that the good fixture lines still load. + +### Error messages (the brief's error items) + +- mkdir fails with EACCES, EPERM or EROFS: + `notify journal directory cannot be created (CODE): `. +- Directory is a symlink (checked with lstat): + `notify journal directory must not be a symlink: `. The 0700 check + follows it as before. +- Directory not writable (`accessSync` W_OK|X_OK): + `notify journal directory is not writable (CODE): `. +- File not writable on open: `notify journal is not writable (CODE): `. + +Every case is a CliError with exit 3 and names the path. bus-service.sh's +install message adds `mkdir -m 0700 -p /notify/`. +`docs/TOOLS.md` is **unchanged**: it does not quote the install output and +already states the 0700 rule. + +### host.mjs guards + +Both `broker.send({ op: "close" })` sends on the notifier start-failure +paths are now `if (broker.connected) …`. :144 is the no-reply path (a start +timeout, or the notifier exiting before it replies). :150 is the refusal +path. On a closed channel, `ChildProcess.send` emits `error` +(ERR_IPC_CHANNEL_CLOSED). + +There is a remaining window, which I'm recording but did not change. +`connected` stays true after the broker dies until Node processes the +disconnect. If the broker is SIGKILLed and the notifier exits a moment +later, the :144 close can still be sent. When I probed it, the late send +emitted no `error` event in 5 of 5 runs and `connected` was false shortly +afterwards. Passing a callback to that `send` would close the window +completely. I left it out because the brief asked for the guard and no +failure was observed. It is a candidate follow-up if a reviewer wants it. + +### Tests + +- **Reader-capability exposure:** a `node:diagnostics_channel` spy on + `child_process` traps the `send` setter and captures the `{op:"start", + cap}` message the host sends the notifier. host.mjs is not modified. The + test asserts that both `launch.cap` and the reader cap are absent from + `/proc//cmdline` and `environ` for both children and from the state + file. +- **M28:** a second host for the same data root refuses with exit 3, + `a bus host already runs for acme (pid …)`. +- **N5:** `watchChildren` reports a child killed by SIGKILL before the call + as `["broker", null, "SIGKILL"]`. +- **N2 and N4:** a symlinked directory refuses. An append after the file is + swapped for a symlink fails with ELOOP and writes nothing through it. +- **Guard (G150):** the spy SIGKILLs the broker synchronously on the start + send. The notifier still refuses with `/no dmRecipient/`, there is no + `error` event and there is no `close` send. 15/15 runs passed. +- **Guard (G144):** the spy SIGSTOPs the notifier, SIGKILLs the broker, + and SIGKILLs the notifier once the broker's `disconnect` fires. The host + rejects with `notifier exited (null) before it replied`, exit 1, with no + `error` event and no `close` send. 15/15 runs passed, and 10/10 after a + small cleanup. My first version killed both children at once, and the + test caught the window described above. +- **`t.after`** is added to "a notifier that dies". +- **0500 directory and 0500 parent:** modes are restored in try/finally, + not `t.after`. A `t.after` restore runs after `tmp()` cleanup and leaked a + directory once; I removed that directory. Both tests skip when running as + root. +- **Discord lock test:** `deadPid()` spawns a node child, reaps it, and + loops until `!pidAlive(pid)`. pid_max here is 4194304, so `2 ** 22 - 7` + can be a live pid. I replaced all five uses in that file, not only :120. +- **Trackers boot test:** copied byte-identical to + `packages/cli/tests/trackers-boot.test.mjs`. No import changes were needed. + Sage's tracked original in `agents/sage/work/s4-follow-up/` is left for + Sage to remove, because I don't edit another seat's directory. + +## Gate + +Round 1 ran the full gate. I then added the G144 test, which changes only +`host.test.mjs`. I rebuilt the patch, re-applied it to a clean checkout of +the tree (manifest OK), and re-ran the cli node suite: **60/0** +(`out/node-cli.txt`). The other node suites and the `test-*.sh` suites below +are from round 1, on the identical source files. + +Detached worktree `/mnt/storage/scratch/rocko-r45/tree` at 521597bb with +`build.patch` applied; manifest check OK (`out/manifest-check-tree.txt`). +`run.sh` ran every `packages/*/tests` suite, then every `scripts/test-*.sh`, +in sequence. test-task and test-release ran with +`DOCKER_HOST=unix:///nonexistent-rocko-r45-docker.sock`. Load average was +4.34 at the start and 5.65 at the end. + +| Suite | Pass / fail | +|---|---| +| node business / bus / cli / control-board | 60/0 · 67/0 · 60/0 (round 2; 59/0 in round 1) · 124/0 | +| node discord / ledger / mosaic / queue | 178/0 · 78/0 · 69/0 · 148/0 | +| node seat / tasks / webui | 19/0 · 51/0 · 14/0 | +| node conversation | 149/3: K1, K3, K10 (row 46, expected) | +| test-auth / conductor / config / discord | 15/0 · 17/0 · 24/0 · 66/0 | +| test-extension-package / foundation / queue / release | 18/0 · 44/0 · 27/0 · 4/0 | +| test-task | 26/2: "user recall run succeeds", "recalled user name" | + +Unpatched base, worktree `/mnt/storage/scratch/rocko-r45/base` at 521597bb +(`out/base-*.txt`): + +- test-task: 26/2, the same two live-recall failures. They need Docker, which + the gate withholds. They are not caused by this patch. +- conversation: 150/2 (K1, K3). The patch doesn't touch + `packages/conversation`, so K10 is intermittent. It is one of the brief's + row 46 exceptions. + +## Mutants (`mutants.sh`, `out/mutants.txt`, `out/mut-*.txt`) + +Each mutant is one perl substitution in the gate tree, followed by a run of +the cli and discord node suites. Only a `fail` counts as a kill. Every +failing test is named, and none is a flaky bystander. The table comes from +the round 2 run, where all 18 were killed. In round 1, before its test +existed, G144 survived. After the run the tree re-checked OK +(`out/manifest-check-mut.txt`). Load average was 3.98 at the start and 5.38 +at the end. + +| Id | Mutation | Result | Killed by | +|---|---|---|---| +| N2 | `lstatSync(dir)` → `statSync(dir)` | killed | symlinked directory refuses | +| N4 | drop `O_NOFOLLOW` from the append open | killed | append after symlink swap | +| N5 | death check ignores `signalCode` | killed | watchChildren, child died before call | +| M28 | drop the `prior?.live` refusal | killed | second host refuses with exit 3 | +| G150 | drop the guard on the refusal-path close | killed | notifier refuses after broker died | +| G144 | drop the guard on the no-reply-path close | killed | notifier dies before it replies | +| F2a | limit 5 → 6 | killed | five definite refusals stop a DM | +| F2b | 429 counts as definite | killed | 429s/unknowns never count | +| F2c | tick ignores `gaveUp` | killed (2) | crash recovery; five refusals | +| F2d | no crash-recovery give-up in tick | killed | crash recovery | +| F2e | no digest mark | killed | five refusals (digest check) | +| F2f | no give-up after the fifth refusal | killed | five refusals | +| J4a | `at` round-trip check removed | killed | wrong-type table | +| J4b | dm decision type check removed | killed | wrong-type table | +| J4c | `status` integer check removed | killed | wrong-type table | +| J4d | confirmed messageId check removed | killed | wrong-type table | +| E1 | `accessSync` writability check removed | killed | unwritable directory/parent | +| E2 | symlink refusal removed | killed | symlinked directory refuses | + +## Hashes + +- `build.patch`: `4ed9ff61b94b4c9d2426e5703c2e6c0dbcd6bc0c64a88d59347a8b47f4933408` +- `candidate-manifest.sha256`: `b329fdcbdf8cb62659f239359570dfa9771559e3112b743df55429244769ed14` +- `packet-manifest.sha256`: a sorted sha256 list of every packet file except itself. diff --git a/agents/rocko/work/s4-follow-up/base.txt b/agents/rocko/work/s4-follow-up/base.txt new file mode 100644 index 00000000..8f273517 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/base.txt @@ -0,0 +1 @@ +521597bbe0cf51a0a1aa3b7a16f13fc7b59c98c6 diff --git a/agents/rocko/work/s4-follow-up/build.patch b/agents/rocko/work/s4-follow-up/build.patch new file mode 100644 index 00000000..53c7ac34 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/build.patch @@ -0,0 +1,846 @@ +diff --git a/packages/cli/README.md b/packages/cli/README.md +index 589078c3..67dc6618 100644 +--- a/packages/cli/README.md ++++ b/packages/cli/README.md +@@ -145,8 +145,8 @@ inbox through the reader capability and does two things: + - **Digest.** It sends one digest a day at 08:00 America/Chicago. The hour + comes from the IANA zone, so daylight saving time is handled. If the host + starts after 08:00 and the day has no digest yet, the digest goes at once. +- The digest lists the inbox and marks each blocking decision as DM sent or +- DM pending. An empty inbox gets one line. Each message stays within ++ The digest lists the inbox and marks each blocking decision as DM sent, ++ DM pending, or DM refused, not retried. An empty inbox gets one line. Each message stays within + Discord's 2000 characters. + + The notifier only reads the bus. Its memory is the journal +@@ -165,16 +165,29 @@ one line per send attempt: + carries the same Discord nonce, so a retry inside Discord's dedupe window + returns the first message. A DM's nonce comes from the decision id. A + digest's comes from the business and the day. ++- A definite refusal is a DM refused with an HTTP 4xx other than 429. After ++ 5 of them for one decision, the notifier appends one `gave-up` line, logs ++ it once and never sends that DM again; the digest marks the decision "DM ++ refused, not retried". The count comes from the journal, so a restart ++ keeps it. An `unknown` outcome (network, 5xx or 429) retries without a ++ limit (lead decision 72). + - On open, a final line without its newline is a torn write. The notifier + copies those bytes to `torn-.bin` in the same directory (0600, + a new file, synced), then truncates `sent.jsonl` to its last newline and + syncs it. It logs both steps. A crash between the two leaves the torn + tail in place, and the next open repairs it with a second copy. The next + append therefore starts on a line of its own. +-- A malformed complete line refuses with exit 3 and changes nothing. +-- The journal refuses with exit 3 when its directory is looser than 0700 or +- not yours, when `sent.jsonl` is a symlink, or when the file is not a +- regular 0600 file you own. ++- A malformed complete line refuses with exit 3 and changes nothing. Each ++ line is type-checked: `at` an ISO timestamp, `kind` `dm` or `digest`, ++ `decision` a string (required for `dm`, null for `digest`), `day` ++ `YYYY-MM-DD` (required for `digest`), `outcome` one of `confirmed`, ++ `refused`, `unknown` or `gave-up` (`gave-up` only for `dm`), `messageId` ++ a string (required when confirmed) or null, `status` an integer when ++ present. ++- The journal refuses with exit 3 when its directory is looser than 0700, ++ not yours, a symlink or not writable, when `sent.jsonl` is a symlink or ++ not writable, or when the file is not a regular 0600 file you own. The ++ message names the path. + - No Discord channel or user id goes in the journal, a log line or an + error. + +diff --git a/packages/cli/src/host.mjs b/packages/cli/src/host.mjs +index 159160c0..26553cba 100644 +--- a/packages/cli/src/host.mjs ++++ b/packages/cli/src/host.mjs +@@ -141,13 +141,13 @@ export async function startHost({ boot, business, notifier = null, bootTimeoutMs + } catch (e) { + notify.kill("SIGTERM"); + await ended(notify, 5000); +- broker.send({ op: "close" }); ++ if (broker.connected) broker.send({ op: "close" }); + await ended(broker, CLOSE_TIMEOUT_MS); + throw e; + } + if (ok?.ok !== true) { + await ended(notify, 5000); +- broker.send({ op: "close" }); ++ if (broker.connected) broker.send({ op: "close" }); + await ended(broker, CLOSE_TIMEOUT_MS); + throw new CliError(`notifier refused to start: ${typeof ok?.error === "string" ? ok.error : "notifier-refused"}`, 3); + } +diff --git a/packages/cli/src/notifier.mjs b/packages/cli/src/notifier.mjs +index a4bb78d2..0610e1f5 100644 +--- a/packages/cli/src/notifier.mjs ++++ b/packages/cli/src/notifier.mjs +@@ -4,17 +4,22 @@ + // bus; its memory is the journal `/notify//sent.jsonl` + // (0600, in a 0700 directory), one line per send attempt: + // +-// {at, kind: "dm"|"digest", decision, day?, outcome: "confirmed"|"refused"|"unknown", messageId, status?} ++// {at, kind: "dm"|"digest", decision, day?, outcome: "confirmed"|"refused"|"unknown"|"gave-up", messageId, status?} + // + // A decision counts as sent once it has a confirmed line; a day's digest + // likewise. A refused or unknown send is retried with backoff (30 s + // doubling to 30 min): a duplicate costs less than a miss, and Discord's + // nonce folds a retry inside its dedupe window into the first message. ++// The exception is a definite refusal, an HTTP 4xx other than 429 (lead ++// decision 72): after five for one decision, counted from the journal so a ++// restart keeps the count, the notifier appends one `gave-up` line, logs ++// once and stops sending that DM. The digest marks it "DM refused, not ++// retried". Unknown outcomes (network, 5xx, 429) retry without a limit. + // No Discord channel or user id goes in the journal, a log line or an + // error; the Discord side (packages/discord/src/notify.mjs) keeps them. + + import { createHash } from "node:crypto"; +-import { closeSync, constants, fstatSync, fsyncSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs"; ++import { accessSync, closeSync, constants, fstatSync, fsyncSync, ftruncateSync, lstatSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs"; + import { dirname, join } from "node:path"; + import { CliError } from "./errors.mjs"; + import { authorizationLines, optionsLine, shortId } from "./format.mjs"; +@@ -25,6 +30,7 @@ export const POLL_MS = 30000; + const BACKOFF_MS = 30000; + const BACKOFF_MAX_MS = 30 * 60 * 1000; + const LIMIT = 2000; ++export const REFUSAL_LIMIT = 5; + + export const journalPath = (dataRoot, business) => join(dataRoot, "notify", business, "sent.jsonl"); + +@@ -62,14 +68,14 @@ export function dmContent(business, d) { + return clip(lines.join("\n"), LIMIT); + } + +-export function digestContent(business, day, inbox, dmSent) { ++export function digestContent(business, day, inbox, dmSent, dmGaveUp = () => false) { + if (inbox.length === 0) return `Mosaic digest (${business}, ${day}): your inbox is empty.`; + const head = `Mosaic digest (${business}, ${day}): ${inbox.length} open decision(s).`; + const tail = "Run mosaic inbox for the full list."; + const lines = [head]; + let shown = 0; + for (const d of inbox) { +- const mark = d.blocking ? (dmSent(d.id) ? "[blocking, DM sent] " : "[blocking, DM pending] ") : ""; ++ const mark = d.blocking ? (dmSent(d.id) ? "[blocking, DM sent] " : dmGaveUp(d.id) ? "[blocking, DM refused, not retried] " : "[blocking, DM pending] ") : ""; + const line = `- ${mark}${shortId(d.id)} ${d.action}: ${clip(d.question.replace(/\s+/g, " "), 160)}`; + const more = inbox.length - shown - 1; + const reserve = more > 0 ? `\n… and ${more} more.`.length : 0; +@@ -116,9 +122,31 @@ function copyTorn(dir, bytes, date) { + } + } + ++const OUTCOMES = ["confirmed", "refused", "unknown", "gave-up"]; ++const DAY = /^\d{4}-\d{2}-\d{2}$/; ++const UNWRITABLE = ["EACCES", "EPERM", "EROFS"]; ++ ++// The first field of a journal record that has the wrong type, or null ++// (lead decision 72, J4). A confirmed line carries the message id. ++function badField(r) { ++ if (!r || typeof r !== "object" || Array.isArray(r)) return "record"; ++ if (typeof r.at !== "string" || Number.isNaN(Date.parse(r.at)) || new Date(r.at).toISOString() !== r.at) return "at"; ++ if (!["dm", "digest"].includes(r.kind)) return "kind"; ++ if (r.kind === "dm" ? typeof r.decision !== "string" || r.decision === "" : r.decision !== null && r.decision !== undefined) return "decision"; ++ if (r.kind === "digest" ? typeof r.day !== "string" || !DAY.test(r.day) : r.day !== undefined && (typeof r.day !== "string" || !DAY.test(r.day))) return "day"; ++ if (!OUTCOMES.includes(r.outcome) || (r.outcome === "gave-up" && r.kind !== "dm")) return "outcome"; ++ if (r.outcome === "confirmed" ? typeof r.messageId !== "string" : r.messageId !== null && typeof r.messageId !== "string") return "messageId"; ++ if (r.status !== undefined && !Number.isInteger(r.status)) return "status"; ++ return null; ++} ++ ++// A DM refused with an HTTP 4xx other than 429. ++const definite = (r) => r.kind === "dm" && r.outcome === "refused" && Number.isInteger(r.status) && r.status >= 400 && r.status < 500 && r.status !== 429; ++ + // Opens (creating if needed) the journal. The directory must be 0700 or +-// tighter and the file 0600, both owned by this user; a symlinked journal +-// refuses. Every complete line must parse, or the open refuses with exit 3. ++// tighter, writable and not a symlink, and the file 0600, both owned by ++// this user; a symlinked journal refuses. Every complete line must parse ++// and type-check, or the open refuses with exit 3. + // A final line without its newline is a write that never finished (lead + // decision 71): its bytes are copied to torn-.bin, then the journal + // is truncated to its last newline and fsynced, and both steps are logged. +@@ -126,20 +154,42 @@ function copyTorn(dir, bytes, date) { + // both; the second copy is harmless. + export function openJournal(file, { log = () => {}, now = () => new Date() } = {}) { + const dir = dirname(file); +- mkdirSync(dir, { recursive: true, mode: 0o700 }); ++ try { ++ mkdirSync(dir, { recursive: true, mode: 0o700 }); ++ } catch (e) { ++ if (UNWRITABLE.includes(e.code)) throw new CliError(`notify journal directory cannot be created (${e.code}): ${dir}`, 3); ++ if (e.code !== "EEXIST") throw e; ++ } + const ds = lstatSync(dir); ++ if (ds.isSymbolicLink()) throw new CliError(`notify journal directory must not be a symlink: ${dir}`, 3); + if (!ds.isDirectory() || ds.uid !== process.getuid() || (ds.mode & 0o077) !== 0) { + throw new CliError(`notify journal directory must be mode 0700 and owned by this user: ${dir}`, 3); + } ++ try { ++ accessSync(dir, constants.W_OK | constants.X_OK); ++ } catch (e) { ++ if (UNWRITABLE.includes(e.code)) throw new CliError(`notify journal directory is not writable (${e.code}): ${dir}`, 3); ++ throw e; ++ } + let fd; + try { + fd = openSync(file, O_RDWR | O_APPEND | O_CREAT | O_NOFOLLOW, 0o600); + } catch (e) { + if (e.code === "ELOOP") throw new CliError(`notify journal must not be a symlink: ${file}`, 3); ++ if (UNWRITABLE.includes(e.code)) throw new CliError(`notify journal is not writable (${e.code}): ${file}`, 3); + throw e; + } + const sent = new Set(); + const days = new Set(); ++ const refusals = new Map(); ++ const gaveUp = new Set(); ++ const count = (r) => { ++ if (r.kind === "dm" && r.outcome === "gave-up") gaveUp.add(r.decision); ++ if (definite(r)) refusals.set(r.decision, (refusals.get(r.decision) ?? 0) + 1); ++ if (r.outcome !== "confirmed") return; ++ if (r.kind === "dm") sent.add(r.decision); ++ else days.add(r.day); ++ }; + try { + const st = fstatSync(fd); + if (!st.isFile() || st.uid !== process.getuid() || (st.mode & 0o777) !== 0o600) { +@@ -156,12 +206,9 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { + } catch { + r = null; + } +- if (!r || typeof r !== "object" || !["dm", "digest"].includes(r.kind)) { +- throw new CliError(`notify journal line ${i + 1} is malformed: ${file}`, 3); +- } +- if (r.outcome !== "confirmed") return; +- if (r.kind === "dm") sent.add(r.decision); +- else days.add(r.day); ++ const bad = r === null ? "json" : badField(r); ++ if (bad) throw new CliError(`notify journal line ${i + 1} is malformed (${bad}): ${file}`, 3); ++ count(r); + }); + if (end < bytes.length) { + const name = copyTorn(dir, bytes.subarray(end), now()); +@@ -176,6 +223,8 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { + return { + sent, + days, ++ refusals, ++ gaveUp, + append(record) { + const afd = openSync(file, O_WRONLY | O_APPEND | O_NOFOLLOW); + try { +@@ -183,9 +232,7 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { + } finally { + closeSync(afd); + } +- if (record.outcome !== "confirmed") return; +- if (record.kind === "dm") sent.add(record.decision); +- else days.add(record.day); ++ count(record); + }, + }; + } +@@ -202,6 +249,13 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => + return b !== undefined && t < b.next; + } + ++ // One `gave-up` line and one log line; the decision is never sent again. ++ function giveUp(decision, t) { ++ journal.append({ at: new Date(t).toISOString(), kind: "dm", decision, outcome: "gave-up", messageId: null }); ++ backoff.delete(`dm:${decision}`); ++ log(`notify: dm ${shortId(decision)} refused ${journal.refusals.get(decision)} times; gave up, not retried`); ++ } ++ + async function attempt(key, record, message) { + const t = now().getTime(); + try { +@@ -215,6 +269,10 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => + const n = (backoff.get(key)?.n ?? -1) + 1; + backoff.set(key, { n, next: t + Math.min(BACKOFF_MS * 2 ** n, BACKOFF_MAX_MS) }); + journal.append({ at: new Date(t).toISOString(), ...record, outcome: kind, messageId: null, ...(status !== null ? { status } : {}) }); ++ if (record.kind === "dm" && (journal.refusals.get(record.decision) ?? 0) >= REFUSAL_LIMIT) { ++ giveUp(record.decision, t); ++ return false; ++ } + log(`notify: ${record.kind} ${kind}${status !== null ? ` (HTTP ${status})` : ""}; retry in ${Math.round(Math.min(BACKOFF_MS * 2 ** n, BACKOFF_MAX_MS) / 1000)} s`); + return false; + } +@@ -232,7 +290,12 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => + } + const t = now(); + for (const d of list) { +- if (!d.blocking || journal.sent.has(d.id)) continue; ++ if (!d.blocking || journal.sent.has(d.id) || journal.gaveUp.has(d.id)) continue; ++ // A crash between the fifth refusal and its gave-up line. ++ if ((journal.refusals.get(d.id) ?? 0) >= REFUSAL_LIMIT) { ++ giveUp(d.id, t.getTime()); ++ continue; ++ } + const key = `dm:${d.id}`; + if (waiting(key, t.getTime())) continue; + if (await attempt(key, { kind: "dm", decision: d.id }, { content: dmContent(business, d), nonce: dmNonce(d.id) })) done.dms++; +@@ -241,7 +304,7 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => + const { day, hour } = zoned(t, zone); + const key = `digest:${day}`; + if (hour >= DIGEST_HOUR && !journal.days.has(day) && !waiting(key, t.getTime())) { +- const content = digestContent(business, day, list, (id) => journal.sent.has(id)); ++ const content = digestContent(business, day, list, (id) => journal.sent.has(id), (id) => journal.gaveUp.has(id)); + if (await attempt(key, { kind: "digest", decision: null, day }, { content, nonce: digestNonce(business, day) })) done.digest = true; + else done.failed++; + } +diff --git a/packages/cli/tests/host.test.mjs b/packages/cli/tests/host.test.mjs +index 8d20b47f..649031ae 100644 +--- a/packages/cli/tests/host.test.mjs ++++ b/packages/cli/tests/host.test.mjs +@@ -1,6 +1,7 @@ + import { test } from "node:test"; + import assert from "node:assert/strict"; + import { spawn, spawnSync } from "node:child_process"; ++import { subscribe, unsubscribe } from "node:diagnostics_channel"; + import { createServer } from "node:http"; + import { once } from "node:events"; + import { existsSync, readFileSync, statSync, writeFileSync, mkdirSync } from "node:fs"; +@@ -48,6 +49,31 @@ async function until(fn, ms = 8000) { + throw new Error("timed out waiting"); + } + ++// Records every IPC message this process sends to a child it creates while ++// the spy is on, by trapping the `send` that node installs on a new child. ++// The host never exposes the notifier's reader capability; this is how a ++// test sees it. ++function spySends(t, onSend = () => {}) { ++ const sent = []; ++ const onChild = ({ process: child }) => { ++ let send; ++ Object.defineProperty(child, "send", { ++ configurable: true, ++ get: () => send, ++ set(fn) { ++ send = function (m, ...rest) { ++ sent.push(m); ++ onSend(m); ++ return fn.call(this, m, ...rest); ++ }; ++ }, ++ }); ++ }; ++ subscribe("child_process", onChild); ++ t.after(() => unsubscribe("child_process", onChild)); ++ return sent; ++} ++ + const procText = (pid, what) => { + try { + return readFileSync(`/proc/${pid}/${what}`, "utf8"); +@@ -64,8 +90,12 @@ test("the host boots the broker, binds a launch in process, and the notifier DMs + const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); + assert.equal("trackers" in boot, false); + const logs = []; ++ const sends = spySends(t); + const host = await startHost({ boot, business: "acme", notifier: { binding: "test-seat", base: discord.base, pollMs: 100 }, log: (l) => logs.push(l) }); + t.after(() => host.close(0)); ++ const start = sends.find((m) => m?.op === "start"); ++ assert.equal(typeof start?.cap, "string", "the spy saw the notifier's start message"); ++ assert.ok(start.cap.length >= 16); + + const state = JSON.parse(readFileSync(hostFile(f.dataRoot), "utf8")); + assert.equal(statSync(hostFile(f.dataRoot)).mode & 0o777, 0o600); +@@ -84,12 +114,16 @@ test("the host boots the broker, binds a launch in process, and the notifier DMs + assert.ok(discord.requests.every((r) => r.authorized)); + assert.match(discord.dms()[0].body.content, new RegExp(`mosaic decide ${d.id.slice(0, 8)}`)); + +- // No capability in a child's argv or environment, or in the state file. +- for (const pid of Object.values(host.pids)) { +- assert.ok(!procText(pid, "cmdline").includes(launch.cap)); +- assert.ok(!procText(pid, "environ").includes(launch.cap)); ++ // No capability, the launch's or the notifier's reader, in a child's argv ++ // or environment, or in the state file. ++ for (const cap of [launch.cap, start.cap]) { ++ for (const pid of Object.values(host.pids)) { ++ assert.notEqual(procText(pid, "cmdline"), "", `pid ${pid} is readable`); ++ assert.ok(!procText(pid, "cmdline").includes(cap)); ++ assert.ok(!procText(pid, "environ").includes(cap)); ++ } ++ assert.ok(!readFileSync(hostFile(f.dataRoot), "utf8").includes(cap)); + } +- assert.ok(!readFileSync(hostFile(f.dataRoot), "utf8").includes(launch.cap)); + + assert.equal(await host.close(0), 0); + const journal = readFileSync(journalPath(f.dataRoot, "acme"), "utf8"); +@@ -107,6 +141,7 @@ test("a notifier that dies takes the host down with exit 1, so the unit restarts + const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); + const logs = []; + const host = await startHost({ boot, business: "acme", notifier: { binding: "test-seat", base: discord.base, pollMs: 100 }, log: (l) => logs.push(l) }); ++ t.after(() => host.close(0)); + process.kill(host.pids.notifier, "SIGKILL"); + assert.equal(await host.done, 1); + assert.match(logs.join("\n"), /notifier exited \(SIGKILL\); stopping the host/); +@@ -114,6 +149,21 @@ test("a notifier that dies takes the host down with exit 1, so the unit restarts + assert.equal(existsSync(hostFile(f.dataRoot)), false); + }); + ++test("a second host for the same data root refuses with exit 3 while the first runs", async (t) => { ++ const root = tmp(t); ++ const f = fixture(root); ++ makeDeployment(root); ++ const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); ++ const host = await startHost({ boot, business: "acme", log: () => {} }); ++ t.after(() => host.close(0)); ++ const second = startHost({ boot, business: "acme", log: () => {} }); ++ // If the refusal regresses and a second host starts, close it too. ++ t.after(async () => (await second.catch(() => null))?.close(0)); ++ await assert.rejects(second, (e) => e.exitCode === 3 && e.message === `a bus host already runs for acme (pid ${process.pid})`); ++ assert.equal(hostStatus(f.dataRoot).host.live, true, "the first host still runs"); ++ assert.equal(await host.close(0), 0); ++}); ++ + test("a notifier that refuses stops the broker and the host refuses with exit 3", async (t) => { + const root = tmp(t); + const f = fixture(root); +@@ -127,9 +177,68 @@ test("a notifier that refuses stops the broker and the host refuses with exit 3" + assert.equal(existsSync(hostFile(f.dataRoot)), false); + }); + ++test("a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker", async (t) => { ++ const root = tmp(t); ++ const f = fixture(root); ++ makeDeployment(root); ++ const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); ++ const children = []; ++ const onChild = ({ process: child }) => children.push(child); ++ subscribe("child_process", onChild); ++ t.after(() => unsubscribe("child_process", onChild)); ++ const errors = []; ++ // The broker dies as the host sends the notifier its start message. ++ const sends = spySends(t, (m) => { ++ if (m?.op !== "start") return; ++ children[0].on("error", (e) => errors.push(e.code)); ++ children[0].kill("SIGKILL"); ++ }); ++ const started = startHost({ boot, business: "acme", notifier: { binding: "test-seat" }, log: () => {} }); ++ t.after(async () => (await started.catch(() => null))?.close(0)); ++ await assert.rejects(started, (e) => e.exitCode === 3 && /no dmRecipient/.test(e.message)); ++ await new Promise((r) => setImmediate(r)); ++ assert.deepEqual(errors, [], "no close was sent over the closed channel"); ++ assert.equal(sends.filter((m) => m?.op === "close").length, 0); ++}); ++ ++test("a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker", async (t) => { ++ const root = tmp(t); ++ const f = fixture(root); ++ makeDeployment(root); ++ const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); ++ const children = []; ++ const onChild = ({ process: child }) => children.push(child); ++ subscribe("child_process", onChild); ++ t.after(() => unsubscribe("child_process", onChild)); ++ const errors = []; ++ // The broker dies as the host sends the start message. The notifier is ++ // stopped so it cannot reply, and killed once the host has seen the broker ++ // disconnect: the host takes the no-reply path, not the refusal path. ++ const sends = spySends(t, (m) => { ++ if (m?.op !== "start") return; ++ children[0].on("error", (e) => errors.push(e.code)); ++ children[1].kill("SIGSTOP"); ++ children[0].once("disconnect", () => children[1].kill("SIGKILL")); ++ children[0].kill("SIGKILL"); ++ }); ++ const started = startHost({ boot, business: "acme", notifier: { binding: "test-seat" }, log: () => {} }); ++ t.after(async () => (await started.catch(() => null))?.close(0)); ++ await assert.rejects(started, (e) => e.exitCode === 1 && /notifier exited \(null\) before it replied/.test(e.message)); ++ await new Promise((r) => setImmediate(r)); ++ assert.deepEqual(errors, [], "no close was sent over the closed channel"); ++ assert.equal(sends.filter((m) => m?.op === "close").length, 0); ++}); ++ + test("watchChildren reports a child that died before it was called, and one that dies later", async (t) => { + const early = spawn(process.execPath, ["-e", "process.exit(7)"], { stdio: "ignore" }); + await once(early, "exit"); ++ const killed = spawn(process.execPath, ["-e", "setTimeout(() => {}, 60000)"], { stdio: "ignore" }); ++ await once(killed, "spawn"); ++ killed.kill("SIGKILL"); ++ await once(killed, "exit"); ++ const signalled = []; ++ watchChildren({ broker: killed }, (...d) => signalled.push(d)); ++ assert.deepEqual(signalled, [["broker", null, "SIGKILL"]], "a death by signal before the watch is not lost either"); + const late = spawn(process.execPath, ["-e", "setTimeout(() => {}, 60000)"], { stdio: "ignore" }); + t.after(() => late.kill("SIGKILL")); + await once(late, "spawn"); +diff --git a/packages/cli/tests/notifier.test.mjs b/packages/cli/tests/notifier.test.mjs +index 99127662..aa81ac69 100644 +--- a/packages/cli/tests/notifier.test.mjs ++++ b/packages/cli/tests/notifier.test.mjs +@@ -1,12 +1,15 @@ + import { test } from "node:test"; + import assert from "node:assert/strict"; +-import { appendFileSync, chmodSync, mkdirSync, readdirSync, readFileSync, statSync, symlinkSync, writeFileSync } from "node:fs"; ++import { appendFileSync, chmodSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs"; + import { dirname, join } from "node:path"; +-import { createNotifier, digestContent, digestNonce, dmNonce, journalPath, openJournal, runLoop, zoned } from "../src/notifier.mjs"; ++import { createNotifier, digestContent, digestNonce, dmNonce, journalPath, openJournal, REFUSAL_LIMIT, runLoop, zoned } from "../src/notifier.mjs"; + import { RestOutcome } from "../../discord/src/rest.mjs"; + import { broker, tmp } from "./helpers.mjs"; + ++const AT = "2026-10-08T12:00:00.000Z"; ++ + // Discord-side fake: records sends, answers from a script (default: ok). ++// An entry is "ok", "refused" (HTTP 403), "unknown", or {kind, status}. + function fakeDirect(script = []) { + const sends = []; + let n = 0; +@@ -16,7 +19,8 @@ function fakeDirect(script = []) { + sends.push(m); + const next = script.shift() ?? "ok"; + if (next === "ok") return { messageId: `30000000000000${String(++n).padStart(4, "0")}` }; +- throw new RestOutcome(next, `dm: ${next}`, { status: next === "refused" ? 403 : null }); ++ const { kind, status } = typeof next === "string" ? { kind: next, status: next === "refused" ? 403 : null } : next; ++ throw new RestOutcome(kind, `dm: ${kind}`, { status }); + }, + }; + } +@@ -101,6 +105,72 @@ test("a failed DM is journaled, backs off, and is retried until it lands", async + assert.ok(s.logs.some((l) => /retry in 30 s/.test(l))); + }); + ++// 05:00Z is 00:00 Chicago: eight hours of polls before the digest is due. ++const MIDNIGHT = "2026-10-08T05:00:00Z"; ++const PAST_BACKOFF = 31 * 60_000; ++ ++test("five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count", async (t) => { ++ assert.equal(REFUSAL_LIMIT, 5); ++ const s = setup(t, MIDNIGHT, ["refused", "refused", "refused", { kind: "refused", status: 404 }, { kind: "refused", status: 400 }]); ++ const d = s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); ++ for (let i = 0; i < 3; i++) { ++ assert.equal((await s.notifier.tick()).failed, 1); ++ s.time.advance(PAST_BACKOFF); ++ } ++ const after = s.make(); ++ for (let i = 0; i < 2; i++) { ++ assert.equal((await after.tick()).failed, 1, "the restart did not reset the count"); ++ s.time.advance(PAST_BACKOFF); ++ } ++ assert.equal(s.direct.sends.length, 5); ++ assert.deepEqual(s.journal().map((r) => r.outcome), ["refused", "refused", "refused", "refused", "refused", "gave-up"]); ++ assert.deepEqual(s.journal().at(-1), { at: s.journal().at(-1).at, kind: "dm", decision: d.id, outcome: "gave-up", messageId: null }); ++ assert.deepEqual(await after.tick(), { dms: 0, digest: false, failed: 0 }); ++ assert.deepEqual(await s.make().tick(), { dms: 0, digest: false, failed: 0 }); ++ assert.equal(s.direct.sends.length, 5, "never sent again, before or after a restart"); ++ assert.equal(s.journal().length, 6); ++ const gave = s.logs.filter((l) => /gave up/.test(l)); ++ assert.deepEqual(gave, [`notify: dm ${d.id.slice(0, 8)} refused 5 times; gave up, not retried`]); ++ assert.equal(s.logs.filter((l) => /retry in/.test(l)).length, 4, "the fifth refusal logs the give-up, not a retry"); ++ s.time.clock.t = new Date("2026-10-08T13:00:00Z"); ++ assert.equal((await s.make().tick()).digest, true); ++ assert.match(s.direct.sends.at(-1).content, new RegExp(`\\[blocking, DM refused, not retried\\] ${d.id.slice(0, 8)} git\\.push\\.protected`)); ++}); ++ ++test("429s, 5xx-style unknowns and refusals without a status never count toward the limit", async (t) => { ++ const script = [ ++ ...Array(6).fill({ kind: "refused", status: 429 }), ++ ...Array(3).fill("unknown"), ++ { kind: "refused", status: null }, ++ ...Array(REFUSAL_LIMIT - 1).fill("refused"), ++ "ok", ++ ]; ++ const s = setup(t, MIDNIGHT, [...script]); ++ s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); ++ for (let i = 0; i < script.length; i++) { ++ await s.notifier.tick(); ++ s.time.advance(PAST_BACKOFF); ++ } ++ assert.equal(s.direct.sends.length, script.length); ++ assert.equal(s.journal().at(-1).outcome, "confirmed"); ++ assert.ok(!s.journal().some((r) => r.outcome === "gave-up")); ++ assert.ok(!s.logs.some((l) => /gave up/.test(l))); ++}); ++ ++test("a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing", async (t) => { ++ const s = setup(t, MIDNIGHT); ++ const d = s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); ++ const file = journalPath(s.dataRoot, "demo"); ++ const line = { at: AT, kind: "dm", decision: d.id, outcome: "refused", messageId: null, status: 403 }; ++ appendFileSync(file, `${JSON.stringify(line)}\n`.repeat(REFUSAL_LIMIT)); ++ assert.deepEqual(await s.make().tick(), { dms: 0, digest: false, failed: 0 }); ++ assert.equal(s.direct.sends.length, 0); ++ assert.equal(s.journal().at(-1).outcome, "gave-up"); ++ assert.equal(s.logs.filter((l) => /gave up/.test(l)).length, 1); ++ await s.make().tick(); ++ assert.equal(s.journal().length, REFUSAL_LIMIT + 1, "one gave-up line only"); ++}); ++ + test("the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd", async (t) => { + const s = setup(t, "2026-10-08T12:59:00Z"); + const d = s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); +@@ -150,7 +220,7 @@ const FRAGMENT = '{"at":"x","kind":"dm","dec'; + + test("the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly", (t) => { + const file = journalPath(tmp(t), "demo"); +- openJournal(file).append({ at: "x", kind: "dm", decision: "a", outcome: "confirmed", messageId: "1" }); ++ openJournal(file).append({ at: AT, kind: "dm", decision: "a", outcome: "confirmed", messageId: "1" }); + const good = readFileSync(file); + appendFileSync(file, FRAGMENT); + const logs = []; +@@ -164,7 +234,7 @@ test("the journal: a torn tail is copied out and truncated, so an append after i + assert.equal(logs.length, 2); + assert.match(logs[0], /copied a torn final line \(26 bytes\) to torn-20261008T235212345Z\.bin/); + assert.match(logs[1], /truncated .* to its last newline/); +- j.append({ at: "y", kind: "dm", decision: "b", outcome: "confirmed", messageId: "2" }); ++ j.append({ at: AT, kind: "dm", decision: "b", outcome: "confirmed", messageId: "2" }); + const again = []; + assert.deepEqual([...openJournal(file, { log: (l) => again.push(l) }).sent], ["a", "b"]); + assert.deepEqual(again, [], "nothing torn the second time"); +@@ -172,7 +242,7 @@ test("the journal: a torn tail is copied out and truncated, so an append after i + + test("the journal: a crash between the copy and the truncate leaves a tail the next open repairs", (t) => { + const file = journalPath(tmp(t), "demo"); +- openJournal(file).append({ at: "x", kind: "dm", decision: "a", outcome: "confirmed", messageId: "1" }); ++ openJournal(file).append({ at: AT, kind: "dm", decision: "a", outcome: "confirmed", messageId: "1" }); + appendFileSync(file, FRAGMENT); + const now = () => new Date("2026-10-08T23:52:12.345Z"); + // The log after step 1 throws: the process dies before step 2. +@@ -182,7 +252,7 @@ test("the journal: a crash between the copy and the truncate leaves a tail the n + const j = openJournal(file, { now }); + assert.deepEqual(tornFiles(file), ["torn-20261008T235212345Z-1.bin", "torn-20261008T235212345Z.bin"], "a second copy, the first kept"); + for (const n of tornFiles(file)) assert.equal(readFileSync(join(dirname(file), n), "utf8"), FRAGMENT); +- j.append({ at: "y", kind: "dm", decision: "b", outcome: "confirmed", messageId: "2" }); ++ j.append({ at: AT, kind: "dm", decision: "b", outcome: "confirmed", messageId: "2" }); + assert.deepEqual([...openJournal(file).sent], ["a", "b"]); + }); + +@@ -222,6 +292,89 @@ test("the journal: a loose file mode, a loose directory or a symlinked journal r + assert.throws(() => openJournal(linked), (e) => e.exitCode === 3 && /must not be a symlink/.test(e.message)); + }); + ++test("the journal: a line with a wrong type refuses with exit 3 and names the field", (t) => { ++ const file = journalPath(tmp(t), "demo"); ++ openJournal(file); ++ const dm = { at: AT, kind: "dm", decision: "a", outcome: "confirmed", messageId: "1" }; ++ const digest = { at: AT, kind: "digest", decision: null, day: "2026-10-08", outcome: "confirmed", messageId: "2" }; ++ const bad = [ ++ ["record", [1]], ++ ["at", { ...dm, at: "x" }], ++ ["at", { ...dm, at: "2026-10-08" }], ++ ["at", { ...dm, at: 1 }], ++ ["kind", { ...dm, kind: "dg" }], ++ ["decision", { ...dm, decision: 7 }], ++ ["decision", { ...dm, decision: "" }], ++ ["decision", { ...digest, decision: "a" }], ++ ["day", { ...digest, day: undefined }], ++ ["day", { ...digest, day: "2026-10-8" }], ++ ["day", { ...dm, day: 20261008 }], ++ ["outcome", { ...dm, outcome: "sent" }], ++ ["outcome", { ...digest, outcome: "gave-up", messageId: null }], ++ ["messageId", { ...dm, messageId: null }], ++ ["messageId", { ...dm, outcome: "refused", messageId: 1 }], ++ ["status", { ...dm, outcome: "refused", messageId: null, status: "403" }], ++ ["status", { ...dm, outcome: "refused", messageId: null, status: 403.5 }], ++ ]; ++ for (const [field, r] of bad) { ++ const text = `${JSON.stringify(dm)}\n${JSON.stringify(r)}\n`; ++ writeFileSync(file, text); ++ assert.throws(() => openJournal(file), (e) => e.exitCode === 3 && e.message === `notify journal line 2 is malformed (${field}): ${file}`, `${field}: ${JSON.stringify(r)}`); ++ assert.equal(readFileSync(file, "utf8"), text, "a refusal changes nothing"); ++ } ++ const good = [dm, digest, { ...dm, outcome: "refused", messageId: null, status: 403 }, { ...dm, outcome: "gave-up", messageId: null }, { ...digest, outcome: "unknown", messageId: null }]; ++ writeFileSync(file, good.map((r) => `${JSON.stringify(r)}\n`).join("")); ++ const j = openJournal(file); ++ assert.deepEqual([...j.sent], ["a"]); ++ assert.deepEqual([...j.days], ["2026-10-08"]); ++ assert.deepEqual([...j.gaveUp], ["a"]); ++ assert.deepEqual([...j.refusals], [["a", 1]]); ++}); ++ ++test("the journal: a symlinked directory refuses and says it is a link", (t) => { ++ const root = tmp(t); ++ const real = join(root, "real"); ++ mkdirSync(real, { mode: 0o700 }); ++ const file = journalPath(root, "demo"); ++ mkdirSync(dirname(dirname(file)), { mode: 0o700 }); ++ symlinkSync(real, dirname(file)); ++ assert.throws(() => openJournal(file), (e) => e.exitCode === 3 && e.message === `notify journal directory must not be a symlink: ${dirname(file)}`); ++ assert.deepEqual(readdirSync(real), [], "nothing was created through the link"); ++}); ++ ++test("the journal: an append after the file was swapped for a symlink refuses and writes nothing through it", (t) => { ++ const root = tmp(t); ++ const file = journalPath(root, "demo"); ++ const j = openJournal(file); ++ const other = join(root, "elsewhere.jsonl"); ++ writeFileSync(other, "", { mode: 0o600 }); ++ rmSync(file); ++ symlinkSync(other, file); ++ assert.throws(() => j.append({ at: AT, kind: "dm", decision: "a", outcome: "confirmed", messageId: "1" }), (e) => e.code === "ELOOP"); ++ assert.equal(readFileSync(other, "utf8"), ""); ++}); ++ ++test("the journal: a directory it cannot write or create refuses with exit 3 and names the path", { skip: process.getuid() === 0 && "root ignores the modes" }, (t) => { ++ const root = tmp(t); ++ const file = journalPath(root, "demo"); ++ mkdirSync(dirname(file), { recursive: true, mode: 0o700 }); ++ // Modes are restored in finally: tmp()'s cleanup runs first among the after hooks. ++ chmodSync(dirname(file), 0o500); ++ try { ++ assert.throws(() => openJournal(file), (e) => e.exitCode === 3 && e.message === `notify journal directory is not writable (EACCES): ${dirname(file)}`); ++ } finally { ++ chmodSync(dirname(file), 0o700); ++ } ++ const parent = join(root, "notify"); ++ const other = journalPath(root, "acme"); ++ chmodSync(parent, 0o500); ++ try { ++ assert.throws(() => openJournal(other), (e) => e.exitCode === 3 && e.message === `notify journal directory cannot be created (EACCES): ${dirname(other)}`); ++ } finally { ++ chmodSync(parent, 0o700); ++ } ++}); ++ + test("digest content stays within Discord's 2000 characters", () => { + const inbox = Array.from({ length: 60 }, (_, i) => ({ id: `${String(i).padStart(8, "0")}-x`, action: "deploy", question: "q".repeat(300), blocking: i % 2 === 0 })); + const text = digestContent("demo", "2026-10-08", inbox, () => true); +diff --git a/packages/cli/tests/trackers-boot.test.mjs b/packages/cli/tests/trackers-boot.test.mjs +new file mode 100644 +index 00000000..b7710013 +--- /dev/null ++++ b/packages/cli/tests/trackers-boot.test.mjs +@@ -0,0 +1,66 @@ ++// Sage's row 39 gate check, not part of the candidate: the S4 host boots the ++// S3 adapter through the real process.mjs when bootConfig emits trackers. ++import { test } from "node:test"; ++import assert from "node:assert/strict"; ++import { chmodSync, mkdirSync, writeFileSync } from "node:fs"; ++import { join } from "node:path"; ++import { Client } from "../../bus/src/client.mjs"; ++import { bootConfig, loadSystem } from "../src/config.mjs"; ++import { startHost, startTimeOf } from "../src/host.mjs"; ++import { writeJson } from "../../business/tests/helpers.mjs"; ++import { FakeVikunja } from "../../tasks/src/fake.mjs"; ++import { SCOPES } from "../../tasks/tests/world.mjs"; ++import { fixture, tmp } from "./helpers.mjs"; ++ ++test("bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja", async (t) => { ++ const root = tmp(t); ++ const fake = new FakeVikunja(); ++ const srv = await fake.listen(); ++ t.after(() => srv.close()); ++ const owner = fake.user("svc-acme"); ++ const bots = {}; ++ for (const r of ["sync", "pm", "cto", "coder", "reviewer"]) bots[r] = fake.user(`bot-acme-${r}`, { owner }); ++ const project = fake.project("Acme", owner); ++ for (const r of ["pm", "cto", "coder", "reviewer"]) fake.share(project, bots[r], 1); ++ fake.share(project, bots.sync, 0); ++ fake.install(project); ++ ++ const f = fixture(root, "acme", (doc) => { ++ doc.vars["tracker.baseUrl"] = srv.url; ++ doc.tracker.sync.botId = bots.sync; ++ for (const r of Object.keys(doc.roles)) doc.roles[r].tracker.botId = bots[r]; ++ return doc; ++ }); ++ const put = (file, token) => { ++ writeFileSync(file, token + "\n"); ++ chmodSync(file, 0o600); ++ }; ++ put(f.doc.tracker.sync.credentials.vikunja.file, fake.token(bots.sync, SCOPES.sync)); ++ for (const r of Object.keys(f.doc.roles)) put(f.doc.roles[r].credentials.vikunja.file, fake.token(bots[r], r === "pm" ? SCOPES.pm : SCOPES.worker)); ++ writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": project } }); ++ ++ mkdirSync(f.dataRoot, { recursive: true, mode: 0o700 }); ++ const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); ++ assert.deepEqual(boot.trackers, { acme: { baseUrl: srv.url, project, pollSeconds: 60, reconcileMinutes: 60 } }); ++ const host = await startHost({ boot, business: "acme", log: () => {} }); ++ t.after(() => host.close(0)); ++ ++ const launch = await host.bindLaunch({ business: "acme", role: "pm", run: "pm-run", harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) }); ++ const pm = new Client({ path: host.path, cap: launch.cap }); ++ await pm.call("role.claim"); ++ let code; ++ const end = Date.now() + 15000; ++ do { ++ code = await pm.call("task.close", {}).then(() => "ok", (e) => e.code); ++ if (code !== "tracker-starting") break; ++ await new Promise((r) => setTimeout(r, 100)); ++ } while (Date.now() < end); ++ console.log(`task.close {} answered: ${code}; fake saw ${fake.requests.length} requests, first ${fake.requests.slice(0, 3).map((r) => `${r.method} ${r.path} ${r.status}`).join(", ")}`); ++ assert.ok(fake.requests.some((r) => r.path === "/info"), "the adapter called the fake"); ++ assert.doesNotMatch(code, /^(tracker-|credential-|scope-too-broad)/, "the adapter is ready, so the verb fails on its own arguments"); ++ const before = fake.requests.length; ++ const missing = await pm.call("task.close", { task_ref: `vikunja:${project}/999`, verdict: "gate check" }).then(() => "ok", (e) => e.code); ++ console.log(`task.close on a missing task answered: ${missing}; it made ${fake.requests.slice(before).map((r) => `${r.method} ${r.path} ${r.status}`).join(", ")}`); ++ assert.ok(fake.requests.length > before, "a well-formed verb reached Vikunja through the adapter"); ++ assert.equal(await host.close(0), 0); ++}); +diff --git a/packages/discord/tests/journal.test.mjs b/packages/discord/tests/journal.test.mjs +index e3443510..681d8b83 100644 +--- a/packages/discord/tests/journal.test.mjs ++++ b/packages/discord/tests/journal.test.mjs +@@ -17,6 +17,15 @@ function journal() { + return dir; + } + ++// A pid that is provably dead: a child spawned and reaped here, then ++// confirmed gone with kill(pid, 0). A fixed number can belong to a live process. ++function deadPid() { ++ for (;;) { ++ const { pid } = spawnSync(process.execPath, ["-e", ""], { stdio: "ignore" }); ++ if (Number.isSafeInteger(pid) && !pidAlive(pid)) return pid; ++ } ++} ++ + function publish(dir, rec) { + mkdirSync(lockPath(dir), { recursive: true }); + writeFileSync(ownerPath(dir), JSON.stringify(rec) + "\n", { mode: 0o600 }); +@@ -76,7 +85,7 @@ test("lock: the claim is exclusive; a second start against a live owner refuses" + + test("lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it", () => { + const dir = journal(); +- const dead = { pid: 2 ** 22 - 7, start: "1", boot: bootId() }; ++ const dead = { pid: deadPid(), start: "1", boot: bootId() }; + publish(dir, dead); + assert.equal(stopTarget(dir), null); + assert.throws(() => writePid(dir, process.pid), (err) => err instanceof DiscordError && /which is gone/.test(err.message) && /unlock/.test(err.message)); +@@ -116,7 +125,7 @@ test("lock: a stale lock (dead owner, reused pid, or record without start) refus + assert.throws(() => unlock(dir), /cannot be verified; nothing removed/); + rmSync(stopPath(dir)); + rmSync(lockPath(dir), { recursive: true }); +- publish(dir, { pid: 2 ** 22 - 7, start: "1" }); ++ publish(dir, { pid: deadPid(), start: "1" }); + assert.equal(ownerState(readPid(dir)), "dead"); + unlock(dir); + rmSync(stopPath(dir)); +@@ -227,8 +236,9 @@ test("lock: identity syntax; only canonical unsigned decimal start ticks and low + + test("lock: a process whose start marker or boot id cannot be read refuses to claim", () => { + const dir = journal(); +- assert.equal(processStart(2 ** 22 - 7), null); +- assert.throws(() => writePid(dir, 2 ** 22 - 7), (err) => err instanceof DiscordError && /start time or the boot id/.test(err.message)); ++ const gone = deadPid(); ++ assert.equal(processStart(gone), null); ++ assert.throws(() => writePid(dir, gone), (err) => err instanceof DiscordError && /start time or the boot id/.test(err.message)); + assert.equal(existsSync(lockPath(dir)), false, "nothing was left behind"); + const noBoot = (pid) => ({ start: processStart(pid), boot: null }); + assert.throws(() => writePid(dir, process.pid, { identity: noBoot }), /start time or the boot id/); +@@ -276,7 +286,7 @@ test("lock: four processes racing for the same binding; exactly one claims it an + + test("lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner", async () => { + const dir = journal(); +- const stale = { pid: 2 ** 22 - 7, start: "1", boot: bootId() }; ++ const stale = { pid: deadPid(), start: "1", boot: bootId() }; + publish(dir, stale); + // Several starts race over the stale lock: none may reclaim it. + const r1 = await race(dir, 4, "stale"); +@@ -315,7 +325,7 @@ test("lock: four-party schedule; claims landing inside an unlock's gap never sur + const worker = fileURLToPath(new URL("../fixtures/claim-worker.mjs", import.meta.url)); + const go = join(dir, "go"); + writeFileSync(go, ""); +- const stale = { pid: 2 ** 22 - 7, start: "1", boot: bootId() }; ++ const stale = { pid: deadPid(), start: "1", boot: bootId() }; + publish(dir, stale); + const claims = []; + const claim = (tag) => { +diff --git a/scripts/bus-service.sh b/scripts/bus-service.sh +index 4d8c4b95..a60b3610 100755 +--- a/scripts/bus-service.sh ++++ b/scripts/bus-service.sh +@@ -68,6 +68,7 @@ install_unit() { + fi + cat </notify/ the notifier refuses a looser directory + write /notify//notify.json, mode 0600: + {"notifyVersion": 1, "binding": ""} or "binding": null for no DMs + systemctl --user enable --now mosaic-bus@ start now and at login diff --git a/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 b/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 new file mode 100644 index 00000000..f66cb220 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 @@ -0,0 +1,8 @@ +6c777639fae9a9332726260c18ba62c5b942d56601a7d0e7ffe969b214a6f960 packages/cli/README.md +e63b592d249ab1bebb1b3fbe7e61372270b7858c9b1230e3f2ecf48476ec053e packages/cli/src/host.mjs +3f5bbbfcd3a64db07ceff7aef183f7843d6d541246cc51bf17ae3879eb59ebbd packages/cli/src/notifier.mjs +628deae836f9d79e646bc8bba99ad05ab587339cc3f057ad42caa600b0f00c7b packages/cli/tests/host.test.mjs +ec6dce1b2bf0af9635c0128b11542db8bcdb5f2dae846ce5a7005fa71c9a9b59 packages/cli/tests/notifier.test.mjs +f015ef5ed6ae2fa6ec40b2b0d5148baf23436917f71a7fa5521249864fdde0c4 packages/cli/tests/trackers-boot.test.mjs +219924be715db3cbfc6030c4eafd57b27189d6d971b6da26f719b2f86599d37a packages/discord/tests/journal.test.mjs +bc7abfb98e0ffa8c0d1111069c05ac19d9cc9be3b0ed4d457c4f135f97d08bb1 scripts/bus-service.sh diff --git a/agents/rocko/work/s4-follow-up/files.txt b/agents/rocko/work/s4-follow-up/files.txt new file mode 100644 index 00000000..31c3927d --- /dev/null +++ b/agents/rocko/work/s4-follow-up/files.txt @@ -0,0 +1,8 @@ +packages/cli/README.md +packages/cli/src/host.mjs +packages/cli/src/notifier.mjs +packages/cli/tests/host.test.mjs +packages/cli/tests/notifier.test.mjs +packages/cli/tests/trackers-boot.test.mjs +packages/discord/tests/journal.test.mjs +scripts/bus-service.sh diff --git a/agents/rocko/work/s4-follow-up/mutants.sh b/agents/rocko/work/s4-follow-up/mutants.sh new file mode 100755 index 00000000..14ca568f --- /dev/null +++ b/agents/rocko/work/s4-follow-up/mutants.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Row 45 mutants: N2, N4, N5 and M28 from the row 39 reviews, the F2 limit, +# J4 and the new error paths. Same harness as +# agents/filbert/work/slice1-s4-review/mutants.sh: one perl substitution +# each, restored after its run. A kill counts only `fail`; the cancelled +# count is printed too, and the output names every failing test. +# Usage: mutants.sh +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 'packages/cli/tests/*.test.mjs' 'packages/discord/tests/*.test.mjs' > "$O/mut-$id.txt" 2>&1 + local f c; f=$(grep -E '^ℹ fail ' "$O/mut-$id.txt" | awk '{print $3}'); c=$(grep -E '^ℹ cancelled ' "$O/mut-$id.txt" | awk '{print $3}') + local which; which=$(sed -n '/^✖ failing tests:/,$p' "$O/mut-$id.txt" | grep -E '^✖ ' | grep -v 'failing tests' | sed 's/ ([0-9.]*ms)$//' | sort -u | tr '\n' ';') + if [ "${f:-?}" = 0 ]; then echo "$id SURVIVED (cancelled ${c:-?})"; else echo "$id killed (fail $f, cancelled ${c:-?}) $which"; fi + mv "$file.orig" "$file" +} +NT=packages/cli/src/notifier.mjs +HS=packages/cli/src/host.mjs +run N2 $NT 's/lstatSync, mkdirSync/lstatSync, statSync, mkdirSync/; s/const ds = lstatSync\(dir\);/const ds = statSync(dir);/' +run N4 $NT 's/O_WRONLY \| O_APPEND \| O_NOFOLLOW\)/O_WRONLY | O_APPEND)/' +run N5 $HS 's/if \(child\.exitCode !== null \|\| child\.signalCode !== null\) onDeath/if (child.exitCode !== null) onDeath/' +run M28 $HS 's/ if \(prior\?\.live\) throw new CliError\([^\n]*\n//' +run G150 $HS 's/(if \(ok\?\.ok !== true\) \{\n\s*await ended\(notify, 5000\);\n\s*)if \(broker\.connected\) /$1/' +run G144 $HS 's/(notify\.kill\("SIGTERM"\);\n\s*await ended\(notify, 5000\);\n\s*)if \(broker\.connected\) /$1/' +run F2a $NT 's/export const REFUSAL_LIMIT = 5;/export const REFUSAL_LIMIT = 6;/' +run F2b $NT 's/ && r\.status !== 429;/;/' +run F2c $NT 's/ \|\| journal\.gaveUp\.has\(d\.id\)\) continue;/) continue;/' +run F2d $NT 's/if \(\(journal\.refusals\.get\(d\.id\) \?\? 0\) >= REFUSAL_LIMIT\) \{/if (false) {/' +run F2e $NT 's/ : dmGaveUp\(d\.id\) \? "\[blocking, DM refused, not retried\] "//' +run F2f $NT 's/ if \(record\.kind === "dm" && \(journal\.refusals\.get\(record\.decision\) \?\? 0\) >= REFUSAL_LIMIT\) \{/ if (false) {/' +run J4a $NT 's/ \|\| new Date\(r\.at\)\.toISOString\(\) !== r\.at\) return "at";/) return "at";/' +run J4b $NT 's/typeof r\.decision !== "string" \|\| r\.decision === ""/false/' +run J4c $NT 's/if \(r\.status !== undefined && !Number\.isInteger\(r\.status\)\) return "status";//' +run J4d $NT 's/r\.outcome === "confirmed" \? typeof r\.messageId !== "string" : //' +run E1 $NT 's/accessSync\(dir, constants\.W_OK \| constants\.X_OK\);//' +run E2 $NT 's/ if \(ds\.isSymbolicLink\(\)\) throw[^\n]*\n//' diff --git a/agents/rocko/work/s4-follow-up/out/base-node-conversation.txt b/agents/rocko/work/s4-follow-up/out/base-node-conversation.txt new file mode 100644 index 00000000..97750fab --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/base-node-conversation.txt @@ -0,0 +1,190 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (153.746759ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (7.784605ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (4.144815ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (166.426621ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (241.738789ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (175.405896ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (19.158132ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (20.470794ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.73781ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9837.331291ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (40771.630758ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (167.467634ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (104.420259ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (260.70778ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (278.246273ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (279.982807ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (44.582363ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (124.210757ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (39.21486ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (118.478531ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (28.512816ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (66.257461ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (63.37671ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.451994ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.822083ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (2.533541ms) +✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2453.596253ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (133.461009ms) +✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2169.732929ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4753.51382ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (3015.867804ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2821.477795ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2560.990513ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5307.957066ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (1690.955029ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (1331.178006ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (2289.358054ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (443.243493ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (175.516822ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (254.589459ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3114.38952ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (52.688797ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (164.698278ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (93.468012ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (40.783091ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (32.851607ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (25.042487ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (47.449941ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (21.23609ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.790056ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (22.513453ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (128.184526ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (41.237234ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (23.996737ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (37.896102ms) +✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (34.642719ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.875435ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (33.784407ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (48.64611ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (41.304836ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (27.388046ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.610859ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.442332ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.509431ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.185304ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (378.84344ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (60.668832ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (88.718645ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (154.442717ms) +✔ H4: self-takeover is refused (25.295334ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (97.648608ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (93.134482ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (27.283589ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (90.391453ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (132.006689ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (18.680476ms) +✔ H13: a retry with the same request ID and different text is refused (14.630305ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (137.933419ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (69.503201ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (18.488925ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (55.801538ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (52.666046ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (12.758511ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (117.520914ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.740774ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (464.817062ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (355.552569ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (333.392946ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2430.079532ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (476.626319ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.29351ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.823709ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.891417ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.959212ms) +✔ F1: an unreadable fork is never merged into another branch's history (2.409827ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.598438ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (1.010206ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (2.780127ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (5.610015ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.643614ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (7.546061ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.701031ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (8.76656ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.291219ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (2.393268ms) +✔ F9: registrations never add or redirect a root (1.560785ms) +✔ F10: a header cwd naming another project is refused (3.395293ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.833621ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.757559ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.902579ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.509379ms) +✔ F13: compaction is a marker in place, then the retained content (0.873772ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (764.977066ms) +✔ fragments never cut a surrogate pair and keep an empty string (5.227965ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.887197ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.608025ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.284438ms) +✔ a seat directory without search permission refuses that root, not the catalogue (2.300259ms) +✔ every page and cursor is a valid CHAT-01 record (876.06977ms) +✔ the engine pin holds for the installed package (2.75492ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (381.62069ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (372.059171ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (76.35646ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (52.808455ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (29.596354ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.978671ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (40.685824ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (26.470834ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (118.815266ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (57.556349ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1530.983348ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (14.788022ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (69.682312ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (14.206497ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (15.455575ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (31.017307ms) +✔ N10: fake conformance (32.810275ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (26.776932ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (18.993357ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (65.733731ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (29.100256ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (29.428952ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (21.553505ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (12.809566ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (27.483881ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (105.434227ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (138.972954ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (85.341949ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (15.696829ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (16.038262ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (16.215135ms) +✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (49.811955ms) +ℹ tests 152 +ℹ suites 0 +ℹ pass 150 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 55022.350456 + +✖ failing tests: + +test at packages/conversation/tests/cohort.test.mjs:139:1 +✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2453.596253ms) + AssertionError [ERR_ASSERTION]: the escaped child is a listed member + at TestContext. (file:///mnt/storage/scratch/rocko-r45/base/packages/conversation/tests/cohort.test.mjs:151:12) + at async Test.run (node:internal/test_runner/test:1409:7) + at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at packages/conversation/tests/cohort.test.mjs:176:1 +✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2169.732929ms) + AssertionError [ERR_ASSERTION]: the member ignored TERM + at TestContext. (file:///mnt/storage/scratch/rocko-r45/base/packages/conversation/tests/cohort.test.mjs:190:12) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/base-suite-task.txt b/agents/rocko/work/s4-follow-up/out/base-suite-task.txt new file mode 100644 index 00000000..85aa46f0 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/base-suite-task.txt @@ -0,0 +1,33 @@ +OK valid task validates (exit 0) +OK unknown task key exits 2 (exit 2) +OK unsupported taskVersion exits 2 (exit 2) +OK invalid task id exits 2 (exit 2) +OK empty prompt exits 2 (exit 2) +OK NUL in expectExact exits 2 (exit 2) +OK out-of-range timeout exits 2 (exit 2) +OK missing mission file exits 4 (exit 4) +OK task with valid mission validates (exit 0) +OK invalid mission exits 2 (exit 2) +OK validate missing task exits 4 (exit 4) +OK validation does not modify the task file +OK prune dry-run exits 0 (exit 0) +OK dry-run deleted nothing +OK prune --keep=2 --yes removes oldest (exit 0) +OK kept exactly 2 newest runs +OK newest run kept, oldest pruned +OK append-only receipt written (3 entries) +OK sessions/workspaces untouched by prune +OK prune with invalid keep exits 4 (exit 4) +skip adapter seam cases (docker daemon unavailable) +skip workspace/capability cases (docker daemon unavailable) +skip live task cases (docker unavailable) +OK onboard without name exits 4 (non-interactive) (exit 4) +OK onboard --name renders profile (exit 0) +OK profile written +OK canon structure: required filled, optional placeholdered +OK canon sections present +FAIL user recall run succeeds (exit 1) +FAIL recalled user name (response: ) +OK no agent identity on headless run + +selftest: 26 passed, 2 failed diff --git a/agents/rocko/work/s4-follow-up/out/load-end-r2.txt b/agents/rocko/work/s4-follow-up/out/load-end-r2.txt new file mode 100644 index 00000000..89cce05b --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/load-end-r2.txt @@ -0,0 +1 @@ + 08:14:31 up 33 days, 9:49, 5 users, load average: 5.38, 6.86, 6.38 diff --git a/agents/rocko/work/s4-follow-up/out/load-end.txt b/agents/rocko/work/s4-follow-up/out/load-end.txt new file mode 100644 index 00000000..2b1a7a14 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/load-end.txt @@ -0,0 +1 @@ + 08:07:46 up 33 days, 9:42, 5 users, load average: 5.65, 7.22, 5.62 diff --git a/agents/rocko/work/s4-follow-up/out/load-start-r2.txt b/agents/rocko/work/s4-follow-up/out/load-start-r2.txt new file mode 100644 index 00000000..43502ea6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/load-start-r2.txt @@ -0,0 +1 @@ + 08:13:31 up 33 days, 9:48, 5 users, load average: 3.98, 7.07, 6.41 diff --git a/agents/rocko/work/s4-follow-up/out/load-start.txt b/agents/rocko/work/s4-follow-up/out/load-start.txt new file mode 100644 index 00000000..12731645 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/load-start.txt @@ -0,0 +1 @@ + 08:04:03 up 33 days, 9:38, 5 users, load average: 4.34, 9.58, 5.81 diff --git a/agents/rocko/work/s4-follow-up/out/manifest-check-mut.txt b/agents/rocko/work/s4-follow-up/out/manifest-check-mut.txt new file mode 100644 index 00000000..25064faa --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/manifest-check-mut.txt @@ -0,0 +1,8 @@ +packages/cli/README.md: OK +packages/cli/src/host.mjs: OK +packages/cli/src/notifier.mjs: OK +packages/cli/tests/host.test.mjs: OK +packages/cli/tests/notifier.test.mjs: OK +packages/cli/tests/trackers-boot.test.mjs: OK +packages/discord/tests/journal.test.mjs: OK +scripts/bus-service.sh: OK diff --git a/agents/rocko/work/s4-follow-up/out/manifest-check-tree.txt b/agents/rocko/work/s4-follow-up/out/manifest-check-tree.txt new file mode 100644 index 00000000..25064faa --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/manifest-check-tree.txt @@ -0,0 +1,8 @@ +packages/cli/README.md: OK +packages/cli/src/host.mjs: OK +packages/cli/src/notifier.mjs: OK +packages/cli/tests/host.test.mjs: OK +packages/cli/tests/notifier.test.mjs: OK +packages/cli/tests/trackers-boot.test.mjs: OK +packages/discord/tests/journal.test.mjs: OK +scripts/bus-service.sh: OK diff --git a/agents/rocko/work/s4-follow-up/out/mut-E1.txt b/agents/rocko/work/s4-follow-up/out/mut-E1.txt new file mode 100644 index 00000000..8e515de2 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-E1.txt @@ -0,0 +1,281 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (37.582072ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (39.118859ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (22.39365ms) +✔ decide prints a declining choice as declining (21.884386ms) +✔ an unknown outcome is reported once and never resent (19.138442ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (25.194489ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (25.069966ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (20.658963ms) +✔ every human command refuses inside an agent run before it touches the bus (19.02862ms) +✔ usage errors exit 4; no business and no host is a usage error (25.159957ms) +✔ agents and tasks print through the broker (27.433998ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (4.647518ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (104.468131ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.473238ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (65.078595ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (82.569083ms) +✔ a business without tracker.baseUrl gets no trackers entry (77.189096ms) +✔ an unknown business and a broken system config refuse with exit 3 (78.66824ms) +✔ empty views say so (1.31889ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.717368ms) +✔ tasks print the tracker fields the snapshot carries (0.292069ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1036.100057ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (166.880964ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (86.069782ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (153.466029ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (143.924424ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (93.953635ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (27.070795ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.934948ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (247.777929ms) +✔ bus start refuses with exit 3 without a notifier config (106.794138ms) +✔ bus start runs until bus stop; status reports it while it runs (654.225946ms) +✔ bus-service.sh renders the unit and installs it into a given directory (43.704745ms) +✔ zoned uses the IANA zone across DST (30.321559ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (46.793145ms) +✔ two blocking decisions get two DMs with different nonces (22.381489ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.464757ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (21.915219ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (41.429847ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (33.327933ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (24.011326ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (29.201518ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (25.877083ms) +✔ an inbox read failure is logged and the next poll retries (2.21007ms) +✔ no Discord id reaches the journal or the log (30.195966ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (5.572269ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (12.550013ms) +✔ the journal: a whole file that is one torn line truncates to empty (4.188259ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.041871ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (3.52701ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.248863ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.981486ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.80728ms) +✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path (5.32643ms) +✔ digest content stays within Discord's 2000 characters (0.630322ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.170117ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (395.852096ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (66.719116ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2295.924683ms) +✔ busExit and refuseInsideAgent (0.417178ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.371621ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.726195ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.872159ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.53128ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.372834ms) +✔ 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.78667ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.838891ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.790716ms) +✔ authorize: open channel, listed user (2.272538ms) +✔ authorize: wrong guild (0.219111ms) +✔ authorize: no guild (DM) (0.181056ms) +✔ authorize: unlisted channel (0.298149ms) +✔ authorize: unknown channel, no info (0.283628ms) +✔ authorize: thread of listed parent (0.211795ms) +✔ authorize: thread of unlisted parent (0.163882ms) +✔ authorize: text channel that is not a thread and not listed (0.140675ms) +✔ authorize: unlisted user (0.194174ms) +✔ authorize: no author (0.500087ms) +✔ authorize: bot author (listed id, bot flag) (0.260496ms) +✔ authorize: system author (0.121045ms) +✔ authorize: the bot itself (0.113225ms) +✔ authorize: webhook (0.099137ms) +✔ authorize: mention channel without mention (0.220813ms) +✔ authorize: mention channel with bot mention (0.156671ms) +✔ authorize: mention channel with @everyone only (0.262392ms) +✔ authorize: mention channel mentioning someone else (0.104753ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.12819ms) +✔ authorize: private thread under mention channel, mentioned (0.1304ms) +✔ authorize: private thread under mention channel, not mentioned (0.078928ms) +✔ authorize: thread in another guild per channel info (0.077933ms) +✔ authorize: not an object (0.074428ms) +✔ authorize: no id (0.085666ms) +✔ authorize: oversize content is accepted and flagged (0.09731ms) +✔ authorize: exactly the limit is not oversize (0.072026ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.52564ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.155784ms) +✔ binding: a complete binding validates and is frozen (2.861489ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.559364ms) +✔ binding: empty allowlists refuse (0.413276ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.352546ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.669688ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.505364ms) +✔ binding: file must be 0600, regular, not a symlink (1.574307ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.38879ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (127.969866ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.107523ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (430.884057ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (239.109273ms) +✔ cli: run refuses when STOP is present, before any network use (150.671822ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.985085ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.447798ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (20.086686ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (20.647621ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.578622ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.306951ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (2.031887ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.303037ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.446552ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.884563ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.724558ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.155942ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.14996ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (34.052853ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (10.694575ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (18.477773ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (5.934981ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.779415ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.883219ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.915952ms) +✔ 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 (7.000171ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.346679ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.259981ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.831411ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.266317ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.658781ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.836759ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.187727ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.442673ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (5.044197ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (2.937936ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.341962ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (4.23219ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.478842ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (54.147454ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (53.219298ms) +✔ engine: one prompt, one turn, text and usage come back (58.694747ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (373.472186ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (242.817582ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (116.016435ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.779213ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (135.164216ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (215.983172ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.602581ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (3.146486ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.980092ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (436.042214ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.229871ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.386364ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.602238ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.889263ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.531979ms) +✔ gateway: op 9 resumable resumes (0.389936ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.901757ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.789676ms) +✔ gateway: close() is final and unparseable frames are ignored (0.44579ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.330882ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (78.665549ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (162.34658ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.441751ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (92.079401ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (92.201168ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (116.714187ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (233.963473ms) +✔ git: push pushes the named branch only and reports up to date (80.43671ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (111.80945ms) +✔ git: the credential helper answers get over https from a private file and nothing else (245.815162ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (911.710307ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.181231ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (104.569189ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.242238ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.676ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (105.347867ms) +✔ 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 (364.975437ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.543512ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.301787ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.15153ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (48.036606ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (168.668103ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (104.652705ms) +✔ notices: a kind is recorded per UTC day and found again (0.570491ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.617857ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.913509ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.242077ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (61.439649ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (86.148184ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.686208ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.201967ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (774.129613ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.406666ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.405756ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.742561ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.801065ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.392577ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.928723ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.658379ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.924081ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.634231ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.864676ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.28349ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.545977ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.493661ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.115488ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.564605ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.715809ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.608316ms) +✔ 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.902751ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.375939ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.23034ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (3.444858ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (7.594811ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (8.462805ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.762502ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.381281ms) +✔ tools: listing and search caps hold (11.574267ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.816758ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (4.871471ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.441468ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.50274ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.743042ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.631808ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.74157ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.469516ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.346862ms) +✔ 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.168036ms) +✔ 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.460302ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.263948ms) +✔ 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.006669ms) +✔ 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.613053ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3105.930867 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:357:1 +✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path (5.32643ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + CliError: notify journal is not writable (EACCES): /mnt/storage/scratch/tmp/mosaic-cli-kdoUwM/notify/demo/sent.jsonl + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:364:12) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: CliError: notify journal is not writable (EACCES): /mnt/storage/scratch/tmp/mosaic-cli-kdoUwM/notify/demo/sent.jsonl + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:179:44) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:364:25 + at getActual (node:assert:580:5) + at strict.throws (node:assert:728:24) + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:364:12) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12), + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-E2.txt b/agents/rocko/work/s4-follow-up/out/mut-E2.txt new file mode 100644 index 00000000..2fc40433 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-E2.txt @@ -0,0 +1,281 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (30.040857ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (44.031844ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (25.521798ms) +✔ decide prints a declining choice as declining (18.448352ms) +✔ an unknown outcome is reported once and never resent (19.594957ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (18.498298ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (21.6223ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.060265ms) +✔ every human command refuses inside an agent run before it touches the bus (23.980572ms) +✔ usage errors exit 4; no business and no host is a usage error (25.821891ms) +✔ agents and tasks print through the broker (21.302803ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (5.191776ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (86.432511ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (65.359911ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (62.688322ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.818616ms) +✔ a business without tracker.baseUrl gets no trackers entry (61.473301ms) +✔ an unknown business and a broken system config refuse with exit 3 (81.14929ms) +✔ empty views say so (1.247009ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.495542ms) +✔ tasks print the tracker fields the snapshot carries (0.252556ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (978.938242ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (175.857201ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (83.350544ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (142.446188ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (148.415892ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (69.750004ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.56054ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.706707ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (237.955053ms) +✔ bus start refuses with exit 3 without a notifier config (93.775195ms) +✔ bus start runs until bus stop; status reports it while it runs (619.406985ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.733736ms) +✔ zoned uses the IANA zone across DST (25.931799ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (52.297888ms) +✔ two blocking decisions get two DMs with different nonces (27.607911ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (1.437047ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (25.287988ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (31.694473ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (30.367633ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.583974ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (21.871506ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (20.358967ms) +✔ an inbox read failure is logged and the next poll retries (1.345764ms) +✔ no Discord id reaches the journal or the log (27.328615ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (4.772294ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (8.660812ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.692467ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.630703ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.347217ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.655013ms) +✖ the journal: a symlinked directory refuses and says it is a link (1.518457ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.097035ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.8063ms) +✔ digest content stays within Discord's 2000 characters (0.495779ms) +✔ runLoop never overlaps ticks and stops after the one in flight (113.191003ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (381.731938ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (58.744272ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2266.469496ms) +✔ busExit and refuseInsideAgent (0.428836ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.259311ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.849222ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.618066ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.635753ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (22.434533ms) +✔ 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.766907ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.904736ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (6.516886ms) +✔ authorize: open channel, listed user (2.078307ms) +✔ authorize: wrong guild (0.207962ms) +✔ authorize: no guild (DM) (0.309551ms) +✔ authorize: unlisted channel (0.206069ms) +✔ authorize: unknown channel, no info (0.283278ms) +✔ authorize: thread of listed parent (0.260015ms) +✔ authorize: thread of unlisted parent (0.27857ms) +✔ authorize: text channel that is not a thread and not listed (0.146677ms) +✔ authorize: unlisted user (1.206967ms) +✔ authorize: no author (0.301948ms) +✔ authorize: bot author (listed id, bot flag) (0.155986ms) +✔ authorize: system author (0.136158ms) +✔ authorize: the bot itself (0.099989ms) +✔ authorize: webhook (0.116965ms) +✔ authorize: mention channel without mention (0.166508ms) +✔ authorize: mention channel with bot mention (1.722734ms) +✔ authorize: mention channel with @everyone only (0.280333ms) +✔ authorize: mention channel mentioning someone else (0.102797ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.101319ms) +✔ authorize: private thread under mention channel, mentioned (0.092686ms) +✔ authorize: private thread under mention channel, not mentioned (0.068996ms) +✔ authorize: thread in another guild per channel info (0.077879ms) +✔ authorize: not an object (0.069214ms) +✔ authorize: no id (0.070198ms) +✔ authorize: oversize content is accepted and flagged (0.092155ms) +✔ authorize: exactly the limit is not oversize (0.058919ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.473043ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.156411ms) +✔ binding: a complete binding validates and is frozen (2.716913ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.329452ms) +✔ binding: empty allowlists refuse (0.406112ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.391167ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.731766ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.380989ms) +✔ binding: file must be 0600, regular, not a symlink (1.482254ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.457412ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (137.894892ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.897938ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (478.829084ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (297.114053ms) +✔ cli: run refuses when STOP is present, before any network use (142.164503ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.53397ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.340864ms) +✔ 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 (22.242352ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.510822ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.686991ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.395439ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.790842ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.229263ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.655754ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.887962ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.247204ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.379891ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.319519ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.93649ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (10.159228ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (8.608831ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.095543ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.053165ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.464877ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.875045ms) +✔ 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 (5.084866ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.587149ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.713606ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.131098ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.748559ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.955557ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.171814ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.089446ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.314552ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.626794ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.643684ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.921142ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.695221ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.447412ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (76.43499ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (52.610502ms) +✔ engine: one prompt, one turn, text and usage come back (51.791561ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (367.908634ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (257.043191ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.3616ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.974167ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (135.449764ms) +✔ 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.242274ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.199395ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.267142ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.468445ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.956703ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.933239ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.632083ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.514287ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.746903ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.698553ms) +✔ gateway: op 9 resumable resumes (0.397925ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.955243ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.561457ms) +✔ gateway: close() is final and unparseable frames are ignored (0.530669ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (88.851159ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (81.5538ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (121.234753ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.360911ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (98.478995ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (163.324706ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (179.213143ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (256.717195ms) +✔ git: push pushes the named branch only and reports up to date (74.587149ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (94.916669ms) +✔ git: the credential helper answers get over https from a private file and nothing else (212.22541ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (866.156154ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.389196ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (97.291287ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.174036ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.641479ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (75.114768ms) +✔ 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 (462.716435ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.610411ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (35.104544ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (2.03876ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (56.205868ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (168.553258ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (94.54537ms) +✔ notices: a kind is recorded per UTC day and found again (0.477242ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.300582ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.153649ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.750578ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (58.506297ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (58.30982ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (42.801788ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (87.093836ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (862.339269ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.673374ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.708752ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.588565ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.819016ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.884038ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.451236ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.230881ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.618372ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.44822ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (31.133985ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.622951ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (11.151142ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.592303ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.764418ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.973262ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.801258ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.476568ms) +✔ 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.827655ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.691844ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.28847ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.795542ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.491307ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.500309ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.524577ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.60283ms) +✔ tools: listing and search caps hold (11.17589ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.995235ms) +✔ 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.603592ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.290775ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.929431ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.040271ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.42525ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.294143ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.713924ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.971639ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1021.312801ms) +✔ 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.802749ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.25962ms) +✔ 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.086818ms) +✔ 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.530428ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2979.66389 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:334:1 +✖ the journal: a symlinked directory refuses and says it is a link (1.518457ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + CliError: notify journal directory must be mode 0700 and owned by this user: /mnt/storage/scratch/tmp/mosaic-cli-Ewejwg/notify/demo + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:341:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: CliError: notify journal directory must be mode 0700 and owned by this user: /mnt/storage/scratch/tmp/mosaic-cli-Ewejwg/notify/demo + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:165:11) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:341:23 + at getActual (node:assert:580:5) + at strict.throws (node:assert:728:24) + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:341:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12), + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-F2a.txt b/agents/rocko/work/s4-follow-up/out/mut-F2a.txt new file mode 100644 index 00000000..8fe649e7 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-F2a.txt @@ -0,0 +1,271 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (36.911794ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (57.937729ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (23.655211ms) +✔ decide prints a declining choice as declining (33.19939ms) +✔ an unknown outcome is reported once and never resent (32.211558ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (31.136881ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (27.922318ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (21.547786ms) +✔ every human command refuses inside an agent run before it touches the bus (31.081489ms) +✔ usage errors exit 4; no business and no host is a usage error (25.566108ms) +✔ agents and tasks print through the broker (22.631474ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.48699ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (93.546525ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (80.966735ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (95.392118ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (76.417851ms) +✔ a business without tracker.baseUrl gets no trackers entry (80.815482ms) +✔ an unknown business and a broken system config refuse with exit 3 (89.590341ms) +✔ empty views say so (1.305944ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.654443ms) +✔ tasks print the tracker fields the snapshot carries (0.289135ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1055.533952ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (187.340685ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (88.441926ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (168.276578ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (145.187007ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (86.230168ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.286542ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.358588ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.401777ms) +✔ bus start refuses with exit 3 without a notifier config (88.349059ms) +✔ bus start runs until bus stop; status reports it while it runs (617.111033ms) +✔ bus-service.sh renders the unit and installs it into a given directory (31.387856ms) +✔ zoned uses the IANA zone across DST (32.531664ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (50.702559ms) +✔ two blocking decisions get two DMs with different nonces (28.455924ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.367868ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (29.450186ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (1.976255ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (46.493102ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (39.432366ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (32.486372ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (36.76367ms) +✔ an inbox read failure is logged and the next poll retries (1.254918ms) +✔ no Discord id reaches the journal or the log (31.964943ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (5.083637ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (7.725657ms) +✔ the journal: a whole file that is one torn line truncates to empty (3.859531ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.965699ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.091272ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (11.853754ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.905926ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.984103ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.806167ms) +✔ digest content stays within Discord's 2000 characters (0.498272ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.593231ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (415.241747ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (65.157853ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2349.186301ms) +✔ busExit and refuseInsideAgent (0.414805ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.373852ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.791594ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.885937ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.530178ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (21.436135ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (9.365948ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (12.680451ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.994212ms) +✔ authorize: open channel, listed user (2.23961ms) +✔ authorize: wrong guild (0.209057ms) +✔ authorize: no guild (DM) (0.382297ms) +✔ authorize: unlisted channel (0.219137ms) +✔ authorize: unknown channel, no info (0.292619ms) +✔ authorize: thread of listed parent (0.221267ms) +✔ authorize: thread of unlisted parent (0.259672ms) +✔ authorize: text channel that is not a thread and not listed (0.150093ms) +✔ authorize: unlisted user (1.235171ms) +✔ authorize: no author (0.71837ms) +✔ authorize: bot author (listed id, bot flag) (0.164811ms) +✔ authorize: system author (0.140085ms) +✔ authorize: the bot itself (0.085204ms) +✔ authorize: webhook (1.06688ms) +✔ authorize: mention channel without mention (0.161976ms) +✔ authorize: mention channel with bot mention (0.149601ms) +✔ authorize: mention channel with @everyone only (0.100979ms) +✔ authorize: mention channel mentioning someone else (0.083815ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.152626ms) +✔ authorize: private thread under mention channel, mentioned (0.100779ms) +✔ authorize: private thread under mention channel, not mentioned (0.088825ms) +✔ authorize: thread in another guild per channel info (0.088174ms) +✔ authorize: not an object (0.075104ms) +✔ authorize: no id (0.072568ms) +✔ authorize: oversize content is accepted and flagged (0.335419ms) +✔ authorize: exactly the limit is not oversize (0.073336ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.530368ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.181539ms) +✔ binding: a complete binding validates and is frozen (2.904808ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.474454ms) +✔ binding: empty allowlists refuse (0.605646ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.956274ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.641474ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.546399ms) +✔ binding: file must be 0600, regular, not a symlink (2.12013ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.11447ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (135.385153ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (8.060178ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (505.679926ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (225.331933ms) +✔ cli: run refuses when STOP is present, before any network use (155.270779ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.858684ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.822429ms) +✔ 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.274165ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (21.330294ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.376416ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.372856ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.704453ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (4.154256ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.420027ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.917313ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.248843ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.425933ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.27971ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (34.665103ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.888441ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (8.189922ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.186713ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (23.907764ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.148968ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.715317ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (3.300563ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (5.81848ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.810434ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.625989ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.982479ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.753379ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.947245ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.898456ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.519206ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.536119ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (4.015856ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.911121ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.892234ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.502148ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (69.790925ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (74.456856ms) +✔ engine: one prompt, one turn, text and usage come back (78.339128ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (385.3505ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (236.101743ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.920189ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.830202ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (131.151186ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (215.154748ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.74027ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.355778ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.658974ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.165924ms) +✔ engine: a malformed JSONL line fails the turn, not the process (28.02058ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (50.521862ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.74531ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.747047ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (1.185579ms) +✔ gateway: op 9 resumable resumes (0.409868ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.147814ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.602938ms) +✔ gateway: close() is final and unparseable frames are ignored (0.674425ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (106.123908ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (122.250287ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (142.508777ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.368561ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (93.711512ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (117.209843ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (99.909988ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (238.677675ms) +✔ git: push pushes the named branch only and reports up to date (78.756038ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (118.81021ms) +✔ git: the credential helper answers get over https from a private file and nothing else (233.264274ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (783.738236ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.736652ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (134.930699ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.344481ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.042473ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (67.420248ms) +✔ 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 (417.402785ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (3.377878ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (32.841526ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.369784ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (44.364495ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (172.797147ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (109.178136ms) +✔ notices: a kind is recorded per UTC day and found again (0.673673ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.910093ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.36967ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.094509ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (66.732603ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (51.468633ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (54.039627ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (95.036228ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (781.067793ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.551514ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (9.116821ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.979913ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (3.58181ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.990891ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.546731ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.82862ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.881906ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.182022ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (32.554137ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.852215ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (11.824641ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.690814ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.809129ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.800737ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1017.88746ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.540209ms) +✔ 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 (10.808732ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (2.009845ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.279141ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (3.71708ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.292333ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.401566ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.591586ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.120305ms) +✔ tools: listing and search caps hold (10.789113ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.219337ms) +✔ 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.99206ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.204852ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.064784ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.470824ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.873215ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.945415ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.634841ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.380527ms) +✔ 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.293578ms) +✔ 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.164799ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.429563ms) +✔ 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.391993ms) +✔ 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.85178ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3046.188974 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:112:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (1.976255ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 6 !== 5 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:113:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: 6, + expected: 5, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-F2b.txt b/agents/rocko/work/s4-follow-up/out/mut-F2b.txt new file mode 100644 index 00000000..6c71b7eb --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-F2b.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (35.809338ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (61.516163ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (22.093249ms) +✔ decide prints a declining choice as declining (21.836263ms) +✔ an unknown outcome is reported once and never resent (17.950709ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (28.103713ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (22.398173ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (26.537612ms) +✔ every human command refuses inside an agent run before it touches the bus (22.128019ms) +✔ usage errors exit 4; no business and no host is a usage error (30.447864ms) +✔ agents and tasks print through the broker (22.834215ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.827223ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (82.245241ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (80.081523ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (71.091522ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (73.932272ms) +✔ a business without tracker.baseUrl gets no trackers entry (63.300201ms) +✔ an unknown business and a broken system config refuse with exit 3 (96.995535ms) +✔ empty views say so (2.34839ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.719817ms) +✔ tasks print the tracker fields the snapshot carries (0.336067ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1036.568564ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (157.540774ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (73.222992ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (134.688555ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (143.460777ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.603607ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.440968ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.202963ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (213.697478ms) +✔ bus start refuses with exit 3 without a notifier config (101.117934ms) +✔ bus start runs until bus stop; status reports it while it runs (605.898068ms) +✔ bus-service.sh renders the unit and installs it into a given directory (29.833009ms) +✔ zoned uses the IANA zone across DST (26.918303ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.091629ms) +✔ two blocking decisions get two DMs with different nonces (36.349978ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (1.255753ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (41.990812ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (41.187076ms) +✖ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (39.405817ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (27.647569ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (26.280346ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (21.870495ms) +✔ an inbox read failure is logged and the next poll retries (1.606275ms) +✔ no Discord id reaches the journal or the log (18.196793ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (8.537083ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (8.940279ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.995992ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.128762ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.294086ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.346849ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.898255ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.129457ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.708252ms) +✔ digest content stays within Discord's 2000 characters (0.481499ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.293002ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (417.533285ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (62.725716ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2326.218148ms) +✔ busExit and refuseInsideAgent (0.402309ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (6.68008ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (3.382952ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.82823ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.537315ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (26.493252ms) +✔ 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.869087ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.470197ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.828975ms) +✔ authorize: open channel, listed user (1.852092ms) +✔ authorize: wrong guild (0.219828ms) +✔ authorize: no guild (DM) (0.170371ms) +✔ authorize: unlisted channel (0.191907ms) +✔ authorize: unknown channel, no info (0.329577ms) +✔ authorize: thread of listed parent (0.236919ms) +✔ authorize: thread of unlisted parent (0.153708ms) +✔ authorize: text channel that is not a thread and not listed (0.136076ms) +✔ authorize: unlisted user (0.850401ms) +✔ authorize: no author (0.300577ms) +✔ authorize: bot author (listed id, bot flag) (0.16224ms) +✔ authorize: system author (0.185399ms) +✔ authorize: the bot itself (0.114483ms) +✔ authorize: webhook (0.10755ms) +✔ authorize: mention channel without mention (0.147574ms) +✔ authorize: mention channel with bot mention (0.148882ms) +✔ authorize: mention channel with @everyone only (0.111457ms) +✔ authorize: mention channel mentioning someone else (0.120736ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.091221ms) +✔ authorize: private thread under mention channel, mentioned (0.086075ms) +✔ authorize: private thread under mention channel, not mentioned (0.078538ms) +✔ authorize: thread in another guild per channel info (0.082719ms) +✔ authorize: not an object (0.069518ms) +✔ authorize: no id (0.075474ms) +✔ authorize: oversize content is accepted and flagged (0.11407ms) +✔ authorize: exactly the limit is not oversize (0.073174ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.518002ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.150507ms) +✔ binding: a complete binding validates and is frozen (3.266078ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.50948ms) +✔ binding: empty allowlists refuse (0.436742ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (2.346238ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.13474ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.501447ms) +✔ binding: file must be 0600, regular, not a symlink (1.782175ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.534295ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (145.325726ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.949024ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (415.47176ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (237.908348ms) +✔ cli: run refuses when STOP is present, before any network use (137.827867ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.790162ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.809129ms) +✔ 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.462271ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (29.202632ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.618021ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.386614ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.199014ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.321777ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (36.327279ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (12.997027ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.406104ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.311505ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.323567ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (34.567235ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.523511ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (6.21761ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.999215ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.36129ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.162488ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.373165ms) +✔ 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 (5.801223ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.830329ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (10.969654ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (4.213571ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.662434ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.693549ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.831632ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.85741ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.2215ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.560786ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.531935ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.008434ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.50528ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.46555ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (61.777537ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (62.513581ms) +✔ engine: one prompt, one turn, text and usage come back (58.557306ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (372.02923ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (243.499589ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.528611ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.918914ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (131.353378ms) +✔ 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.401472ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.892512ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.478111ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.575785ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.724455ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.836549ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.327148ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.904539ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.658181ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.535838ms) +✔ gateway: op 9 resumable resumes (0.423511ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.956917ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.654482ms) +✔ gateway: close() is final and unparseable frames are ignored (0.494117ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (93.939318ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (81.740151ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (152.45589ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.470672ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (94.563316ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (98.876016ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (108.789089ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (224.211268ms) +✔ git: push pushes the named branch only and reports up to date (71.894818ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (99.773727ms) +✔ git: the credential helper answers get over https from a private file and nothing else (206.215453ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (772.64153ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.133856ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (97.07907ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.182929ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.170957ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (66.585825ms) +✔ 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 (383.009997ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.638379ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (30.271734ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.242261ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (51.268989ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (159.733108ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (102.293769ms) +✔ notices: a kind is recorded per UTC day and found again (0.608209ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.054023ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.553149ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.878016ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (63.721283ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (63.370947ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (51.644202ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (86.009972ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (742.853774ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.673686ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.260468ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.77875ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.858328ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.133484ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (11.086093ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.460315ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.934726ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.383464ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.542656ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.819021ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.742775ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.089911ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.138734ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.430962ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1017.382604ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.394604ms) +✔ 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.801686ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.189262ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.18607ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.265707ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.935879ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.35616ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.768507ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.807816ms) +✔ tools: listing and search caps hold (16.130833ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.956079ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.297231ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.488625ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.351752ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.727236ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.432798ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.233255ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.411247ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.553255ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1030.090468ms) +✔ 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.827732ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.25188ms) +✔ 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.881377ms) +✔ 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.459233ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2951.550138 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:140:1 +✖ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (39.405817ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 5 !== 15 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:154:10) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: 5, + expected: 15, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-F2c.txt b/agents/rocko/work/s4-follow-up/out/mut-F2c.txt new file mode 100644 index 00000000..3c0b97ec --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-F2c.txt @@ -0,0 +1,284 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (30.974017ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (47.663494ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (36.338423ms) +✔ decide prints a declining choice as declining (28.430287ms) +✔ an unknown outcome is reported once and never resent (17.526559ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (21.037159ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (25.891335ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (26.095097ms) +✔ every human command refuses inside an agent run before it touches the bus (27.444466ms) +✔ usage errors exit 4; no business and no host is a usage error (19.017214ms) +✔ agents and tasks print through the broker (19.416438ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.984936ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (91.041944ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (64.674911ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (77.032346ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (63.612501ms) +✔ a business without tracker.baseUrl gets no trackers entry (70.53448ms) +✔ an unknown business and a broken system config refuse with exit 3 (87.558803ms) +✔ empty views say so (1.168964ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.41271ms) +✔ tasks print the tracker fields the snapshot carries (0.230356ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1029.46395ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (158.643559ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (77.316736ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (141.05337ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (125.780681ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (81.88852ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.86455ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.893787ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (221.639141ms) +✔ bus start refuses with exit 3 without a notifier config (98.465056ms) +✔ bus start runs until bus stop; status reports it while it runs (657.432996ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.679685ms) +✔ zoned uses the IANA zone across DST (28.191215ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.9936ms) +✔ two blocking decisions get two DMs with different nonces (26.055041ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.336008ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (32.889789ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (28.87508ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (31.206205ms) +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.2688ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (32.1436ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (29.141993ms) +✔ an inbox read failure is logged and the next poll retries (1.466298ms) +✔ no Discord id reaches the journal or the log (24.078396ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (8.393743ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (4.938105ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.790225ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.770636ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.338326ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (5.250981ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.667573ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.777427ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.831372ms) +✔ digest content stays within Discord's 2000 characters (0.47603ms) +✔ runLoop never overlaps ticks and stops after the one in flight (114.479628ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (427.232373ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (63.671553ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2294.658955ms) +✔ busExit and refuseInsideAgent (0.475121ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (4.66743ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.888526ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.666086ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.485259ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.680969ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.403166ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.738244ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.024914ms) +✔ authorize: open channel, listed user (2.152101ms) +✔ authorize: wrong guild (0.220743ms) +✔ authorize: no guild (DM) (0.323828ms) +✔ authorize: unlisted channel (0.215051ms) +✔ authorize: unknown channel, no info (0.202818ms) +✔ authorize: thread of listed parent (0.196809ms) +✔ authorize: thread of unlisted parent (0.144665ms) +✔ authorize: text channel that is not a thread and not listed (0.180461ms) +✔ authorize: unlisted user (0.207932ms) +✔ authorize: no author (0.290736ms) +✔ authorize: bot author (listed id, bot flag) (0.679777ms) +✔ authorize: system author (0.156024ms) +✔ authorize: the bot itself (0.085854ms) +✔ authorize: webhook (0.098312ms) +✔ authorize: mention channel without mention (0.146701ms) +✔ authorize: mention channel with bot mention (0.158809ms) +✔ authorize: mention channel with @everyone only (0.106023ms) +✔ authorize: mention channel mentioning someone else (0.091997ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.079981ms) +✔ authorize: private thread under mention channel, mentioned (0.090015ms) +✔ authorize: private thread under mention channel, not mentioned (0.073924ms) +✔ authorize: thread in another guild per channel info (0.080136ms) +✔ authorize: not an object (0.071873ms) +✔ authorize: no id (0.48855ms) +✔ authorize: oversize content is accepted and flagged (0.09263ms) +✔ authorize: exactly the limit is not oversize (0.074734ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.503124ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (1.982524ms) +✔ binding: a complete binding validates and is frozen (2.91697ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.588867ms) +✔ binding: empty allowlists refuse (0.449432ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.299445ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.978791ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.178874ms) +✔ binding: file must be 0600, regular, not a symlink (1.793977ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.429862ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (137.48477ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.559555ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (460.430143ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (212.969236ms) +✔ cli: run refuses when STOP is present, before any network use (132.105195ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (3.473401ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.851739ms) +✔ 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.697522ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.065998ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.582824ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (2.274563ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (2.566846ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.597846ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.869207ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.985712ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.038249ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (2.230859ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.86252ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.108402ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.570541ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.679177ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.043586ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (8.063775ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.499981ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.128651ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (3.487359ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (4.637193ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.673208ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.899018ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.428591ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.733965ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.87423ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.983748ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.306382ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.814922ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.630332ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.711466ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.735731ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.451324ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (77.611761ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (59.396429ms) +✔ engine: one prompt, one turn, text and usage come back (57.024048ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (360.833471ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (251.873516ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.181243ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.463603ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (130.659273ms) +✔ 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.909563ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.736363ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.556728ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.547357ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.907371ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.323751ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.55764ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (5.83175ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (3.473517ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.537842ms) +✔ gateway: op 9 resumable resumes (0.352223ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.933225ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.537514ms) +✔ gateway: close() is final and unparseable frames are ignored (0.531703ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (106.762097ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (71.596605ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (136.543143ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.477211ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (108.728602ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (114.889784ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (103.987593ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (214.996313ms) +✔ git: push pushes the named branch only and reports up to date (69.59293ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (99.661481ms) +✔ git: the credential helper answers get over https from a private file and nothing else (218.079079ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (779.851627ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.799182ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (102.500565ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.295987ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.156095ms) +✔ 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.370567ms) +✔ 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 (429.858471ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.368809ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (20.849977ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.087212ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.050887ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (147.299626ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (93.604522ms) +✔ notices: a kind is recorded per UTC day and found again (0.482982ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.794072ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.871795ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.886799ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (63.94343ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (53.367112ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (52.595637ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (88.375853ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (711.639235ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.856221ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.503901ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.665927ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.763035ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.98033ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.875834ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.307469ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.817896ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.547941ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (32.294581ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.564114ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.449436ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.157989ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.64574ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.198621ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.269954ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.454039ms) +✔ 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.600835ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.237469ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.197411ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.30572ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.395895ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.12301ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.989673ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.827967ms) +✔ tools: listing and search caps hold (13.795991ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.984657ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.522234ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.468531ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.467316ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.528786ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.476266ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.705889ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.193562ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.777193ms) +✔ 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.406849ms) +✔ 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.638672ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.280727ms) +✔ 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.035503ms) +✔ 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.599244ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 236 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2988.347758 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:112:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (28.87508ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 8 !== 6 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:131:10) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: 8, + expected: 6, + operator: 'strictEqual', + diff: 'simple' + } + +test at packages/cli/tests/notifier.test.mjs:160:1 +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.2688ms) + AssertionError [ERR_ASSERTION]: one gave-up line only + + 7 !== 6 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:171:10) + 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: 7, + expected: 6, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-F2d.txt b/agents/rocko/work/s4-follow-up/out/mut-F2d.txt new file mode 100644 index 00000000..b1daa37d --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-F2d.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (54.320311ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (76.108892ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (24.96531ms) +✔ decide prints a declining choice as declining (28.089663ms) +✔ an unknown outcome is reported once and never resent (17.865686ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (24.161673ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (22.168394ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (23.551558ms) +✔ every human command refuses inside an agent run before it touches the bus (21.030428ms) +✔ usage errors exit 4; no business and no host is a usage error (22.652467ms) +✔ agents and tasks print through the broker (19.192188ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.619957ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (107.141849ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (103.062755ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (62.364747ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (71.304866ms) +✔ a business without tracker.baseUrl gets no trackers entry (71.797192ms) +✔ an unknown business and a broken system config refuse with exit 3 (104.772783ms) +✔ empty views say so (1.295013ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.684853ms) +✔ tasks print the tracker fields the snapshot carries (0.277462ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1062.01507ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (179.079686ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (97.752942ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (148.822575ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (133.36864ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (75.400752ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.039172ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.147036ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (236.9255ms) +✔ bus start refuses with exit 3 without a notifier config (88.842674ms) +✔ bus start runs until bus stop; status reports it while it runs (626.692718ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.760703ms) +✔ zoned uses the IANA zone across DST (27.955296ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (46.595479ms) +✔ two blocking decisions get two DMs with different nonces (38.440988ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.77874ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (52.880854ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (45.669256ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (34.314952ms) +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.619416ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (27.380309ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (24.772794ms) +✔ an inbox read failure is logged and the next poll retries (1.434255ms) +✔ no Discord id reaches the journal or the log (20.733487ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (3.371998ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (4.56623ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.792887ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.026224ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.053653ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.72282ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.949791ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.356153ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.95355ms) +✔ digest content stays within Discord's 2000 characters (0.484189ms) +✔ runLoop never overlaps ticks and stops after the one in flight (116.27952ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (456.532629ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (93.023065ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2325.935429ms) +✔ busExit and refuseInsideAgent (0.465376ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (5.042031ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.674027ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.706293ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.513267ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (39.071539ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (16.756572ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (12.179075ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (4.253596ms) +✔ authorize: open channel, listed user (1.772288ms) +✔ authorize: wrong guild (0.221109ms) +✔ authorize: no guild (DM) (0.192267ms) +✔ authorize: unlisted channel (0.192242ms) +✔ authorize: unknown channel, no info (0.296866ms) +✔ authorize: thread of listed parent (0.196836ms) +✔ authorize: thread of unlisted parent (0.285431ms) +✔ authorize: text channel that is not a thread and not listed (0.139592ms) +✔ authorize: unlisted user (1.499097ms) +✔ authorize: no author (0.434796ms) +✔ authorize: bot author (listed id, bot flag) (0.141252ms) +✔ authorize: system author (0.131924ms) +✔ authorize: the bot itself (0.105212ms) +✔ authorize: webhook (0.101465ms) +✔ authorize: mention channel without mention (1.561995ms) +✔ authorize: mention channel with bot mention (1.11634ms) +✔ authorize: mention channel with @everyone only (0.534757ms) +✔ authorize: mention channel mentioning someone else (0.868437ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.255449ms) +✔ authorize: private thread under mention channel, mentioned (0.438112ms) +✔ authorize: private thread under mention channel, not mentioned (0.557388ms) +✔ authorize: thread in another guild per channel info (0.262378ms) +✔ authorize: not an object (0.107849ms) +✔ authorize: no id (0.321704ms) +✔ authorize: oversize content is accepted and flagged (0.307594ms) +✔ authorize: exactly the limit is not oversize (0.342669ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (2.064896ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.297446ms) +✔ binding: a complete binding validates and is frozen (2.821451ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.510268ms) +✔ binding: empty allowlists refuse (0.640011ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.411625ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.133729ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.469192ms) +✔ binding: file must be 0600, regular, not a symlink (2.794814ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (3.337076ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (170.035675ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.36089ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (502.61482ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (232.10205ms) +✔ cli: run refuses when STOP is present, before any network use (161.880379ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.355099ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.426985ms) +✔ 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 (28.933249ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (29.137392ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.626706ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.427654ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (3.70041ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (15.169828ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (35.205869ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.871187ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.776459ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (6.233773ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (44.375131ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.809311ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.010319ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.415791ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.504976ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.772268ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.939113ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.961824ms) +✔ 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.593519ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (4.313891ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.810403ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.848556ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.836732ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (5.191637ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.882983ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.197826ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (2.065002ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.602633ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.330284ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.775292ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.275132ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.540355ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (94.312495ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (72.872213ms) +✔ engine: one prompt, one turn, text and usage come back (59.695856ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (366.29491ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.905323ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.074427ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (243.08647ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (131.705299ms) +✔ 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.341407ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.733899ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.308705ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.49694ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.390175ms) +✔ engine: a malformed JSONL line fails the turn, not the process (28.39639ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (58.584345ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.802122ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.780056ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.710052ms) +✔ gateway: op 9 resumable resumes (0.401096ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.126929ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.634918ms) +✔ gateway: close() is final and unparseable frames are ignored (0.590188ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (130.859679ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (70.19481ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.21238ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.414483ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (111.33558ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (140.946792ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (116.916303ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (231.28888ms) +✔ git: push pushes the named branch only and reports up to date (90.25353ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (101.894764ms) +✔ git: the credential helper answers get over https from a private file and nothing else (236.298187ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (787.036234ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (15.953705ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (138.00148ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.549813ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.317924ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (59.645496ms) +✔ 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 (433.762427ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.401483ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (36.332977ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.198167ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (48.018321ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (160.045571ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (107.880971ms) +✔ notices: a kind is recorded per UTC day and found again (0.716367ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.192777ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.133871ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.190418ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (59.670856ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (53.451698ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (43.603413ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (92.710409ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (809.523438ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.010859ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.053414ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.650854ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.922361ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.962364ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.494812ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.562222ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.480774ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.243768ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.757408ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.038443ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.505882ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.434462ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.670388ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.407246ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1010.231573ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.491376ms) +✔ 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.576902ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.409451ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.261697ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.842112ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.498104ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.070708ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (7.126815ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.425643ms) +✔ tools: listing and search caps hold (10.163717ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.679338ms) +✔ 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.043314ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.40505ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.317516ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.777171ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.572825ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.528494ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.078616ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.791486ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1025.740878ms) +✔ 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.934933ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.237789ms) +✔ 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.41001ms) +✔ 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.540569ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3086.089989 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:160:1 +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.619416ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + { + digest: false, + + dms: 1, + - dms: 0, + failed: 0 + } + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:166:10) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: { dms: 1, digest: false, failed: 0 }, + expected: { dms: 0, digest: false, failed: 0 }, + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-F2e.txt b/agents/rocko/work/s4-follow-up/out/mut-F2e.txt new file mode 100644 index 00000000..b4d0ce93 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-F2e.txt @@ -0,0 +1,269 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (38.155082ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (44.658463ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.893936ms) +✔ decide prints a declining choice as declining (22.159064ms) +✔ an unknown outcome is reported once and never resent (15.433145ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.965102ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.663693ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.255728ms) +✔ every human command refuses inside an agent run before it touches the bus (16.159005ms) +✔ usage errors exit 4; no business and no host is a usage error (19.358806ms) +✔ agents and tasks print through the broker (25.695876ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.820287ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (94.399555ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (59.560636ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (64.177886ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (62.264953ms) +✔ a business without tracker.baseUrl gets no trackers entry (61.81234ms) +✔ an unknown business and a broken system config refuse with exit 3 (93.633666ms) +✔ empty views say so (1.315865ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.665843ms) +✔ tasks print the tracker fields the snapshot carries (0.353599ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1000.427317ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (181.301472ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (93.27176ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (137.414953ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (141.848657ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (95.505329ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (26.925453ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.555971ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (237.486632ms) +✔ bus start refuses with exit 3 without a notifier config (117.330067ms) +✔ bus start runs until bus stop; status reports it while it runs (658.019765ms) +✔ bus-service.sh renders the unit and installs it into a given directory (35.529035ms) +✔ zoned uses the IANA zone across DST (29.550095ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (43.805187ms) +✔ two blocking decisions get two DMs with different nonces (29.977568ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.46787ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.827627ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (29.034824ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.965794ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.730528ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (21.150931ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (15.760328ms) +✔ an inbox read failure is logged and the next poll retries (1.46266ms) +✔ no Discord id reaches the journal or the log (16.677846ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (5.032213ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (5.220692ms) +✔ the journal: a whole file that is one torn line truncates to empty (4.010719ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.153268ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.2048ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.977804ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.773011ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.992776ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.670579ms) +✔ digest content stays within Discord's 2000 characters (0.42966ms) +✔ runLoop never overlaps ticks and stops after the one in flight (113.257589ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (395.361001ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (82.855011ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2278.373709ms) +✔ busExit and refuseInsideAgent (0.431799ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.664189ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.812611ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.811908ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.541292ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (24.612386ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.106236ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.980753ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.724449ms) +✔ authorize: open channel, listed user (2.730321ms) +✔ authorize: wrong guild (0.216213ms) +✔ authorize: no guild (DM) (0.30558ms) +✔ authorize: unlisted channel (0.232323ms) +✔ authorize: unknown channel, no info (0.277862ms) +✔ authorize: thread of listed parent (0.209019ms) +✔ authorize: thread of unlisted parent (0.199105ms) +✔ authorize: text channel that is not a thread and not listed (0.164142ms) +✔ authorize: unlisted user (1.982512ms) +✔ authorize: no author (0.581787ms) +✔ authorize: bot author (listed id, bot flag) (0.269499ms) +✔ authorize: system author (0.124357ms) +✔ authorize: the bot itself (0.120655ms) +✔ authorize: webhook (1.019816ms) +✔ authorize: mention channel without mention (0.155318ms) +✔ authorize: mention channel with bot mention (0.146654ms) +✔ authorize: mention channel with @everyone only (0.299638ms) +✔ authorize: mention channel mentioning someone else (0.097446ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.123116ms) +✔ authorize: private thread under mention channel, mentioned (0.100199ms) +✔ authorize: private thread under mention channel, not mentioned (0.084209ms) +✔ authorize: thread in another guild per channel info (0.087858ms) +✔ authorize: not an object (0.07003ms) +✔ authorize: no id (0.068622ms) +✔ authorize: oversize content is accepted and flagged (0.093507ms) +✔ authorize: exactly the limit is not oversize (0.072008ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.504625ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.161233ms) +✔ binding: a complete binding validates and is frozen (4.718139ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.736694ms) +✔ binding: empty allowlists refuse (0.454343ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.285306ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.740505ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.474791ms) +✔ binding: file must be 0600, regular, not a symlink (1.657245ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.771458ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (116.598655ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.336163ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (405.471247ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (206.800948ms) +✔ cli: run refuses when STOP is present, before any network use (140.951322ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (3.143363ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (2.171132ms) +✔ 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 (17.650482ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.18108ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.563245ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.210636ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.306985ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.384482ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.504141ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.497559ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.90795ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.485682ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.606985ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (35.303339ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.798928ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.650383ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.113333ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.392769ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.735746ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.850048ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.147189ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.452023ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.25487ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.183121ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.683411ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.880807ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.875234ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (4.461524ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.464798ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.737679ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.50789ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.045646ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.712047ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.464769ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (69.480024ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (59.333179ms) +✔ engine: one prompt, one turn, text and usage come back (50.422428ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (367.399397ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (230.353265ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.506805ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.750334ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (132.087259ms) +✔ 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.503661ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.96446ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.511114ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.552981ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (431.767537ms) +✔ engine: a malformed JSONL line fails the turn, not the process (32.687689ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (52.548432ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.883786ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.857212ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.656876ms) +✔ gateway: op 9 resumable resumes (0.426298ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.924719ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.583905ms) +✔ gateway: close() is final and unparseable frames are ignored (1.182635ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (79.062868ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (58.187339ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (123.704201ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.480361ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (100.611461ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (101.429319ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (90.962052ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (216.314936ms) +✔ git: push pushes the named branch only and reports up to date (77.197405ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (104.525865ms) +✔ git: the credential helper answers get over https from a private file and nothing else (255.526939ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (894.867161ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.488961ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (95.21334ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.52049ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.448612ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (58.081326ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (365.796007ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.611577ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.196841ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.060337ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (41.85408ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (139.995819ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (88.801792ms) +✔ notices: a kind is recorded per UTC day and found again (0.650585ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.454528ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.161356ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.718151ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (61.47922ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (53.100037ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.706101ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (82.879066ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (723.110751ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.655778ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.362339ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.581319ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.812976ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.786227ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.925145ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (8.075518ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.900435ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.544844ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (33.824092ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.595441ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.842071ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.431921ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.5717ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.042893ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1010.701121ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.493497ms) +✔ 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.172599ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.263049ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.196583ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.28015ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.78376ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.840836ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.213843ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.161054ms) +✔ tools: listing and search caps hold (8.135886ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.837185ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.30883ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (2.093538ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.866409ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (8.151203ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.524361ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.955522ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.976056ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.627809ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1034.133229ms) +✔ 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.533691ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.235049ms) +✔ 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.873039ms) +✔ 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.357248ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3070.23874 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:112:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (29.034824ms) + AssertionError [ERR_ASSERTION]: The input did not match the regular expression /\[blocking, DM refused, not retried\] b84c8033 git\.push\.protected/. Input: + + 'Mosaic digest (demo, 2026-10-08): 1 open decision(s).\n' + + '- [blocking, DM pending] b84c8033 git.push.protected: Push the release?\n' + + 'Run mosaic inbox for the full list.' + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:137:10) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: 'Mosaic digest (demo, 2026-10-08): 1 open decision(s).\n- [blocking, DM pending] b84c8033 git.push.protected: Push the release?\nRun mosaic inbox for the full list.', + expected: /\[blocking, DM refused, not retried\] b84c8033 git\.push\.protected/, + operator: 'match', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-F2f.txt b/agents/rocko/work/s4-follow-up/out/mut-F2f.txt new file mode 100644 index 00000000..3017ae0c --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-F2f.txt @@ -0,0 +1,275 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (39.399613ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (42.752447ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (23.619661ms) +✔ decide prints a declining choice as declining (26.723196ms) +✔ an unknown outcome is reported once and never resent (25.837713ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (20.486234ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.643031ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (21.580481ms) +✔ every human command refuses inside an agent run before it touches the bus (21.160872ms) +✔ usage errors exit 4; no business and no host is a usage error (18.051751ms) +✔ agents and tasks print through the broker (22.166504ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.880253ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (101.606834ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.248461ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (75.77414ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (65.864648ms) +✔ a business without tracker.baseUrl gets no trackers entry (58.375203ms) +✔ an unknown business and a broken system config refuse with exit 3 (107.964014ms) +✔ empty views say so (1.30274ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.751646ms) +✔ tasks print the tracker fields the snapshot carries (0.28704ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1013.596332ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (153.225587ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (72.369039ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (134.955046ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (124.852456ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (83.365093ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.83235ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.11194ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (212.680767ms) +✔ bus start refuses with exit 3 without a notifier config (83.349513ms) +✔ bus start runs until bus stop; status reports it while it runs (611.753218ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.88276ms) +✔ zoned uses the IANA zone across DST (35.741125ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (42.182898ms) +✔ two blocking decisions get two DMs with different nonces (23.477066ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.374022ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (27.804634ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (21.215696ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (40.528558ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.558887ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (22.683419ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (21.833006ms) +✔ an inbox read failure is logged and the next poll retries (1.526351ms) +✔ no Discord id reaches the journal or the log (20.676837ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (6.316386ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (7.532213ms) +✔ the journal: a whole file that is one torn line truncates to empty (3.915338ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.184655ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.268513ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (5.931023ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.789351ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.363442ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.929449ms) +✔ digest content stays within Discord's 2000 characters (0.520921ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.475641ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (400.184074ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (68.610738ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2296.123091ms) +✔ busExit and refuseInsideAgent (0.447727ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.712726ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (3.024231ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.61249ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.517578ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.36331ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (10.687623ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (10.245876ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.024185ms) +✔ authorize: open channel, listed user (2.498851ms) +✔ authorize: wrong guild (0.236205ms) +✔ authorize: no guild (DM) (0.18152ms) +✔ authorize: unlisted channel (0.218218ms) +✔ authorize: unknown channel, no info (0.217544ms) +✔ authorize: thread of listed parent (0.206298ms) +✔ authorize: thread of unlisted parent (0.154946ms) +✔ authorize: text channel that is not a thread and not listed (0.135272ms) +✔ authorize: unlisted user (0.253115ms) +✔ authorize: no author (0.316314ms) +✔ authorize: bot author (listed id, bot flag) (0.189561ms) +✔ authorize: system author (0.14359ms) +✔ authorize: the bot itself (0.09083ms) +✔ authorize: webhook (0.100336ms) +✔ authorize: mention channel without mention (0.28ms) +✔ authorize: mention channel with bot mention (1.877822ms) +✔ authorize: mention channel with @everyone only (0.162538ms) +✔ authorize: mention channel mentioning someone else (0.092843ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.086165ms) +✔ authorize: private thread under mention channel, mentioned (0.098071ms) +✔ authorize: private thread under mention channel, not mentioned (0.07816ms) +✔ authorize: thread in another guild per channel info (0.09546ms) +✔ authorize: not an object (0.0621ms) +✔ authorize: no id (0.065507ms) +✔ authorize: oversize content is accepted and flagged (0.095716ms) +✔ authorize: exactly the limit is not oversize (0.062381ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (1.92612ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.173319ms) +✔ binding: a complete binding validates and is frozen (2.827008ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.379902ms) +✔ binding: empty allowlists refuse (0.449461ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.468679ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.030457ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.530019ms) +✔ binding: file must be 0600, regular, not a symlink (2.043261ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.756996ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (129.019664ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.509946ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (428.99424ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (206.89933ms) +✔ cli: run refuses when STOP is present, before any network use (132.254684ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.97042ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.165299ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (25.192081ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.348133ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.285103ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.294487ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.476894ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.961021ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (35.291646ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (5.320802ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.639498ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.959597ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.522196ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.395495ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.938315ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.679593ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.619776ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.663066ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.320171ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.699162ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.05361ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.647527ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.347797ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.51634ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.020868ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (5.407167ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.956239ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.155157ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (2.18761ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.890901ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.483218ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.870623ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (6.073901ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.711779ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (78.611886ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (60.273514ms) +✔ engine: one prompt, one turn, text and usage come back (57.052337ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (368.629854ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (233.779864ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.194203ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (226.994029ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.117617ms) +✔ 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.048381ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.518495ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.368631ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.502018ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.354452ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.416984ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.291976ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.587568ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.793598ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.540418ms) +✔ gateway: op 9 resumable resumes (0.398196ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.031682ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.630766ms) +✔ gateway: close() is final and unparseable frames are ignored (0.709722ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (94.847126ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (60.644235ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (135.026696ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.521044ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (118.249376ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (81.782602ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (89.371359ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (210.831428ms) +✔ git: push pushes the named branch only and reports up to date (71.549629ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (88.737171ms) +✔ git: the credential helper answers get over https from a private file and nothing else (204.420644ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (782.658523ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (6.122199ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (104.690417ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.630128ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (6.226466ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (62.836983ms) +✔ 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 (347.707446ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.451723ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.9286ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.985477ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.836014ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (155.830596ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (84.268254ms) +✔ notices: a kind is recorded per UTC day and found again (0.428955ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.686483ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.529648ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.844549ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (49.640262ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (53.967422ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.827641ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (99.282963ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (662.247545ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.019352ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.575787ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.810262ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.786533ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.013248ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.114627ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.291963ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (3.750646ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.733421ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.454966ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.599113ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.654825ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.353869ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.720818ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (8.54527ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.157679ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.472066ms) +✔ 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.088956ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.451914ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.225798ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.410104ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.411157ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.303281ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (7.81989ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.608088ms) +✔ tools: listing and search caps hold (13.675057ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.96592ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.459439ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.545129ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.6313ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (7.646125ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.97911ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.789413ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.464866ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.440214ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1038.573157ms) +✔ 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.39088ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.231412ms) +✔ 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.573875ms) +✔ 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.162781ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2893.997443 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:112:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (21.215696ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + + actual - expected + + [ + 'refused', + 'refused', + 'refused', + 'refused', + 'refused', + - 'gave-up' + ] + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:126:10) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: [ 'refused', 'refused', 'refused', 'refused', 'refused' ], + expected: [ 'refused', 'refused', 'refused', 'refused', 'refused', 'gave-up' ], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-G144.txt b/agents/rocko/work/s4-follow-up/out/mut-G144.txt new file mode 100644 index 00000000..224f6c53 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-G144.txt @@ -0,0 +1,271 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (37.492424ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (46.901314ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (19.320263ms) +✔ decide prints a declining choice as declining (20.211541ms) +✔ an unknown outcome is reported once and never resent (20.73056ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (20.168847ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (20.833601ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (23.8767ms) +✔ every human command refuses inside an agent run before it touches the bus (32.884637ms) +✔ usage errors exit 4; no business and no host is a usage error (27.089906ms) +✔ agents and tasks print through the broker (26.096234ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.94948ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (90.828961ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.263936ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (64.080217ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (94.007056ms) +✔ a business without tracker.baseUrl gets no trackers entry (76.77176ms) +✔ an unknown business and a broken system config refuse with exit 3 (94.104967ms) +✔ empty views say so (1.301202ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.50944ms) +✔ tasks print the tracker fields the snapshot carries (0.258581ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1038.216646ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (167.709144ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.033426ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (150.846799ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (142.775643ms) +✖ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (88.736957ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.979586ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.45962ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (214.384562ms) +✔ bus start refuses with exit 3 without a notifier config (95.193623ms) +✔ bus start runs until bus stop; status reports it while it runs (629.211493ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.050807ms) +✔ zoned uses the IANA zone across DST (44.169703ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (42.462522ms) +✔ two blocking decisions get two DMs with different nonces (28.879096ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.351178ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.5297ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (30.368991ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (35.153659ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (36.770199ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (40.033585ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (27.37375ms) +✔ an inbox read failure is logged and the next poll retries (1.45511ms) +✔ no Discord id reaches the journal or the log (39.428261ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (6.474543ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (5.76472ms) +✔ the journal: a whole file that is one torn line truncates to empty (3.507057ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.077401ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.149715ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.470898ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.588062ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.671505ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.546066ms) +✔ digest content stays within Discord's 2000 characters (0.532604ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.366907ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (415.897875ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (87.073395ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2293.572745ms) +✔ busExit and refuseInsideAgent (0.39016ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.44924ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.58996ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.597253ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.521908ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.480035ms) +✔ 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.034758ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.027036ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.179268ms) +✔ authorize: open channel, listed user (2.202193ms) +✔ authorize: wrong guild (0.223048ms) +✔ authorize: no guild (DM) (0.189411ms) +✔ authorize: unlisted channel (0.202054ms) +✔ authorize: unknown channel, no info (0.463484ms) +✔ authorize: thread of listed parent (0.201048ms) +✔ authorize: thread of unlisted parent (0.28348ms) +✔ authorize: text channel that is not a thread and not listed (0.155034ms) +✔ authorize: unlisted user (1.058267ms) +✔ authorize: no author (0.26728ms) +✔ authorize: bot author (listed id, bot flag) (0.15142ms) +✔ authorize: system author (0.138447ms) +✔ authorize: the bot itself (0.096213ms) +✔ authorize: webhook (1.513478ms) +✔ authorize: mention channel without mention (0.242797ms) +✔ authorize: mention channel with bot mention (0.150003ms) +✔ authorize: mention channel with @everyone only (0.24078ms) +✔ authorize: mention channel mentioning someone else (0.111664ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.103927ms) +✔ authorize: private thread under mention channel, mentioned (0.093072ms) +✔ authorize: private thread under mention channel, not mentioned (0.081848ms) +✔ authorize: thread in another guild per channel info (0.078317ms) +✔ authorize: not an object (0.066804ms) +✔ authorize: no id (0.077435ms) +✔ authorize: oversize content is accepted and flagged (0.076551ms) +✔ authorize: exactly the limit is not oversize (0.065789ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.494237ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.183045ms) +✔ binding: a complete binding validates and is frozen (2.52746ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.251136ms) +✔ binding: empty allowlists refuse (0.370771ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.286459ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.67299ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.888812ms) +✔ binding: file must be 0600, regular, not a symlink (1.569365ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.832579ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (126.952943ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.35444ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (467.527415ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (235.435875ms) +✔ cli: run refuses when STOP is present, before any network use (155.159011ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.627766ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.153784ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (20.307154ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.789273ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.508334ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.526541ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.634357ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.310934ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.152699ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.035026ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.684283ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.232098ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.826109ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.744032ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.995623ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.911303ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.688121ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.165624ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.258075ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.840034ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.360198ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (9.426972ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (7.513172ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.491409ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (14.163631ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.516788ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.814796ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.962895ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.358474ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.681658ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.209683ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.773092ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.329708ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.458423ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (71.282406ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (63.938548ms) +✔ engine: one prompt, one turn, text and usage come back (65.967788ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (379.136992ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (238.335738ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.552931ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (249.662987ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (132.42548ms) +✔ 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.052749ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.665387ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.436497ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.518866ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.003583ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.77751ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (50.866344ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.048495ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.080435ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.692223ms) +✔ gateway: op 9 resumable resumes (0.509448ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.071569ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.574532ms) +✔ gateway: close() is final and unparseable frames are ignored (0.580243ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (81.020584ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (96.171888ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (157.089241ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.330816ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (104.691311ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (101.634508ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (104.90505ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (242.523202ms) +✔ git: push pushes the named branch only and reports up to date (86.611862ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (92.380528ms) +✔ git: the credential helper answers get over https from a private file and nothing else (224.989596ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (835.226686ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.370997ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (112.42347ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (5.943609ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (7.244536ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (89.033858ms) +✔ 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 (397.490465ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.572835ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.2874ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.08916ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (47.098882ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (166.45975ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (98.431987ms) +✔ notices: a kind is recorded per UTC day and found again (0.43822ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.40412ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.65507ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.794684ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (85.065322ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (65.083954ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (41.410836ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (91.224724ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (747.8925ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.782205ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.82382ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.743234ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.737002ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.87848ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.369135ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.146023ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.950274ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.911356ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (33.295712ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (19.570912ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (11.011574ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.565558ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.666641ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.768094ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.107302ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.538534ms) +✔ 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.276976ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.478296ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.224197ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.389052ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.700251ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (6.10373ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.198369ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.026638ms) +✔ tools: listing and search caps hold (10.788783ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.990129ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.201741ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.391125ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.618291ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.737268ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.232904ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.356659ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.397313ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.905481ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1028.526748ms) +✔ 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.475888ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.318435ms) +✔ web: web_search asks the instance for json, returns at most ten clean results, and refuses a bad query, a down instance or an unusable answer (4.184267ms) +✔ 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.368008ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3033.968805 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:204:1 +✖ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (88.736957ms) + AssertionError [ERR_ASSERTION]: no close was sent over the closed channel + + actual - expected + + + [ + + 'ERR_IPC_CHANNEL_CLOSED' + + ] + - [] + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:228:10) + 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: [ 'ERR_IPC_CHANNEL_CLOSED' ], + expected: [], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-G150.txt b/agents/rocko/work/s4-follow-up/out/mut-G150.txt new file mode 100644 index 00000000..4cebe7a0 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-G150.txt @@ -0,0 +1,271 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (32.228605ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.278004ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.348264ms) +✔ decide prints a declining choice as declining (16.912534ms) +✔ an unknown outcome is reported once and never resent (17.481236ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (20.695264ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (22.461546ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (20.587981ms) +✔ every human command refuses inside an agent run before it touches the bus (19.735965ms) +✔ usage errors exit 4; no business and no host is a usage error (20.654305ms) +✔ agents and tasks print through the broker (14.565243ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.38852ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (102.567725ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (67.414959ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (57.270434ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (63.527893ms) +✔ a business without tracker.baseUrl gets no trackers entry (51.138233ms) +✔ an unknown business and a broken system config refuse with exit 3 (75.0068ms) +✔ empty views say so (1.345349ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.556933ms) +✔ tasks print the tracker fields the snapshot carries (0.253858ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (964.48508ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (143.652607ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (90.476055ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (134.168937ms) +✖ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (138.183722ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (91.643843ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (28.488994ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.588328ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (237.543324ms) +✔ bus start refuses with exit 3 without a notifier config (94.223396ms) +✔ bus start runs until bus stop; status reports it while it runs (611.398545ms) +✔ bus-service.sh renders the unit and installs it into a given directory (47.266818ms) +✔ zoned uses the IANA zone across DST (29.628864ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (38.474233ms) +✔ two blocking decisions get two DMs with different nonces (22.620799ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.431182ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.575811ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.467542ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (26.178389ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.870431ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (24.774943ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (20.870719ms) +✔ an inbox read failure is logged and the next poll retries (1.936476ms) +✔ no Discord id reaches the journal or the log (24.265066ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.880031ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (6.579526ms) +✔ the journal: a whole file that is one torn line truncates to empty (5.57158ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.215725ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.192825ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.848255ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.82527ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.777662ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.631763ms) +✔ digest content stays within Discord's 2000 characters (0.461135ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.585568ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (371.333244ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (64.934878ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2270.117889ms) +✔ busExit and refuseInsideAgent (0.481272ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.274401ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.827351ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.654273ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.504905ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (24.321214ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.187852ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.354124ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.168845ms) +✔ authorize: open channel, listed user (1.686065ms) +✔ authorize: wrong guild (0.179712ms) +✔ authorize: no guild (DM) (0.156831ms) +✔ authorize: unlisted channel (0.243682ms) +✔ authorize: unknown channel, no info (0.168147ms) +✔ authorize: thread of listed parent (0.162883ms) +✔ authorize: thread of unlisted parent (0.126641ms) +✔ authorize: text channel that is not a thread and not listed (0.121431ms) +✔ authorize: unlisted user (0.74653ms) +✔ authorize: no author (0.294984ms) +✔ authorize: bot author (listed id, bot flag) (0.133181ms) +✔ authorize: system author (0.108172ms) +✔ authorize: the bot itself (0.081269ms) +✔ authorize: webhook (0.095768ms) +✔ authorize: mention channel without mention (0.121743ms) +✔ authorize: mention channel with bot mention (0.123365ms) +✔ authorize: mention channel with @everyone only (0.130847ms) +✔ authorize: mention channel mentioning someone else (0.070893ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.077446ms) +✔ authorize: private thread under mention channel, mentioned (0.074164ms) +✔ authorize: private thread under mention channel, not mentioned (0.060388ms) +✔ authorize: thread in another guild per channel info (0.060283ms) +✔ authorize: not an object (0.060163ms) +✔ authorize: no id (0.052814ms) +✔ authorize: oversize content is accepted and flagged (0.061177ms) +✔ authorize: exactly the limit is not oversize (0.069877ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.464962ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.149034ms) +✔ binding: a complete binding validates and is frozen (2.758522ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.327057ms) +✔ binding: empty allowlists refuse (0.848491ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.393551ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.584087ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.424984ms) +✔ binding: file must be 0600, regular, not a symlink (1.383419ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.447015ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (116.0478ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.616638ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (404.018497ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (205.349169ms) +✔ cli: run refuses when STOP is present, before any network use (123.877473ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.078686ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.022787ms) +✔ 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.724052ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (20.17376ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.435425ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.085901ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.230549ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.874651ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.455002ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.828976ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.020689ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.231898ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.291641ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.385519ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.622368ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (8.888178ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.319892ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.630106ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.22566ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.225906ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.130614ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (4.598557ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.456602ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.996909ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.104301ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.808573ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.057801ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.015527ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.297812ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.561517ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (2.538155ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.862153ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.574018ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.428052ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (61.323542ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (55.184989ms) +✔ engine: one prompt, one turn, text and usage come back (49.635077ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (373.371234ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (232.777044ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.903915ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.929019ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.691271ms) +✔ 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.982165ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.151761ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.776884ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.510358ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (430.530041ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.03044ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.233853ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.280403ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.543335ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.477173ms) +✔ gateway: op 9 resumable resumes (0.375039ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.803216ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.67304ms) +✔ gateway: close() is final and unparseable frames are ignored (0.419021ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (79.408432ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (64.764277ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (137.834111ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (1.076971ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (89.024856ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (79.923571ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (87.595595ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (211.412191ms) +✔ git: push pushes the named branch only and reports up to date (69.13527ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (86.016305ms) +✔ git: the credential helper answers get over https from a private file and nothing else (206.511752ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (887.320392ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.638447ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (91.252181ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.31844ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (7.75022ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (67.283394ms) +✔ 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 (321.083241ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.423785ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.373497ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.551861ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (43.920996ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (148.081404ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (89.461077ms) +✔ notices: a kind is recorded per UTC day and found again (0.447968ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.470555ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.382851ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.813565ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (57.397766ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (53.875844ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.679845ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.808878ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (656.511409ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.54486ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.155851ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.588238ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.71188ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.786562ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (7.433375ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.542964ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.887947ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.475055ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.431777ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.641288ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.683763ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.122666ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.717083ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.322649ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.125836ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.426793ms) +✔ 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.511687ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.43475ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.244943ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.491524ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.979852ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.670757ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.959995ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.407547ms) +✔ tools: listing and search caps hold (10.945615ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.976211ms) +✔ 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.975095ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.41625ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.337924ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.398277ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.125383ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.824525ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.548795ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.527893ms) +✔ 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.106717ms) +✔ 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.286511ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.305898ms) +✔ 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.048986ms) +✔ 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.627593ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2930.347759 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:180:1 +✖ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (138.183722ms) + AssertionError [ERR_ASSERTION]: no close was sent over the closed channel + + actual - expected + + + [ + + 'ERR_IPC_CHANNEL_CLOSED' + + ] + - [] + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:200:10) + 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: [ 'ERR_IPC_CHANNEL_CLOSED' ], + expected: [], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-J4a.txt b/agents/rocko/work/s4-follow-up/out/mut-J4a.txt new file mode 100644 index 00000000..fc7c1bb4 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-J4a.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (40.178394ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (35.863003ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (24.736188ms) +✔ decide prints a declining choice as declining (19.073159ms) +✔ an unknown outcome is reported once and never resent (17.085244ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (19.009292ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (15.976373ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.073234ms) +✔ every human command refuses inside an agent run before it touches the bus (17.122084ms) +✔ usage errors exit 4; no business and no host is a usage error (23.084738ms) +✔ agents and tasks print through the broker (20.525446ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.758998ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (96.537836ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (66.423889ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (57.680444ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (66.148474ms) +✔ a business without tracker.baseUrl gets no trackers entry (62.209784ms) +✔ an unknown business and a broken system config refuse with exit 3 (81.142717ms) +✔ empty views say so (1.35848ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.38045ms) +✔ tasks print the tracker fields the snapshot carries (0.236756ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (971.109222ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (154.493033ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (88.44709ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (137.38753ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (137.585033ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.775458ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.801903ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.673437ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (220.100444ms) +✔ bus start refuses with exit 3 without a notifier config (87.478616ms) +✔ bus start runs until bus stop; status reports it while it runs (592.668832ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.254166ms) +✔ zoned uses the IANA zone across DST (40.883013ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (36.543813ms) +✔ two blocking decisions get two DMs with different nonces (24.964737ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.323865ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.339417ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (26.701409ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (27.199062ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.707002ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (20.970264ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (16.937632ms) +✔ an inbox read failure is logged and the next poll retries (1.303016ms) +✔ no Discord id reaches the journal or the log (22.113291ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (3.51767ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (6.93237ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.649603ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.977266ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.656843ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.942977ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.764619ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.867527ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.932467ms) +✔ digest content stays within Discord's 2000 characters (0.520961ms) +✔ runLoop never overlaps ticks and stops after the one in flight (112.03963ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (375.121884ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (92.605468ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2283.411553ms) +✔ busExit and refuseInsideAgent (0.385315ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (6.699494ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.883197ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.68724ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.525914ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.719514ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.305576ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.276686ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.346813ms) +✔ authorize: open channel, listed user (2.005713ms) +✔ authorize: wrong guild (0.232843ms) +✔ authorize: no guild (DM) (0.328135ms) +✔ authorize: unlisted channel (0.205629ms) +✔ authorize: unknown channel, no info (0.282936ms) +✔ authorize: thread of listed parent (0.1984ms) +✔ authorize: thread of unlisted parent (0.165858ms) +✔ authorize: text channel that is not a thread and not listed (0.133678ms) +✔ authorize: unlisted user (0.437424ms) +✔ authorize: no author (0.281711ms) +✔ authorize: bot author (listed id, bot flag) (0.164005ms) +✔ authorize: system author (0.136004ms) +✔ authorize: the bot itself (0.099494ms) +✔ authorize: webhook (0.12051ms) +✔ authorize: mention channel without mention (0.163499ms) +✔ authorize: mention channel with bot mention (0.163623ms) +✔ authorize: mention channel with @everyone only (0.141232ms) +✔ authorize: mention channel mentioning someone else (0.096826ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.140084ms) +✔ authorize: private thread under mention channel, mentioned (0.161249ms) +✔ authorize: private thread under mention channel, not mentioned (0.086036ms) +✔ authorize: thread in another guild per channel info (0.085184ms) +✔ authorize: not an object (0.059304ms) +✔ authorize: no id (0.064754ms) +✔ authorize: oversize content is accepted and flagged (0.075987ms) +✔ authorize: exactly the limit is not oversize (0.062031ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.481951ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.155196ms) +✔ binding: a complete binding validates and is frozen (3.352913ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.346132ms) +✔ binding: empty allowlists refuse (0.502913ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.356312ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.062133ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.539619ms) +✔ binding: file must be 0600, regular, not a symlink (1.85753ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.497531ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (116.387843ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.214413ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (423.602366ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (189.832448ms) +✔ cli: run refuses when STOP is present, before any network use (134.828463ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.830854ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.172785ms) +✔ 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 (22.045323ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.499086ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.569036ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.165007ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.29847ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.618609ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.571062ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.915062ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.730894ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.34288ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.088159ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.472076ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.540104ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.315309ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.302271ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.267064ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.365998ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.836379ms) +✔ 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 (5.038025ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.770196ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.184327ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.890275ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.363724ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.662734ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.795574ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.998211ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.363229ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.977642ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.241732ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.71357ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (5.024172ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (2.056102ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (68.437918ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (57.675462ms) +✔ engine: one prompt, one turn, text and usage come back (51.164612ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (355.942781ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.327046ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.060805ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.351435ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (130.071807ms) +✔ 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.944482ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.861955ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.294732ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.487353ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (432.88905ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.475298ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.768518ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.321535ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.772038ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.577067ms) +✔ gateway: op 9 resumable resumes (0.34586ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.93422ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.704485ms) +✔ gateway: close() is final and unparseable frames are ignored (0.445703ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (93.797845ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (64.166394ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (128.702975ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.457336ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (100.329641ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (109.242173ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (87.592994ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (202.979136ms) +✔ git: push pushes the named branch only and reports up to date (75.785086ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (108.917459ms) +✔ git: the credential helper answers get over https from a private file and nothing else (211.454357ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (813.086235ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.25015ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (92.71004ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.319005ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.075149ms) +✔ 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.99521ms) +✔ 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 (354.248055ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.512083ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.396756ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.997808ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (41.647727ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (137.663271ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (97.432177ms) +✔ notices: a kind is recorded per UTC day and found again (0.462093ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.439869ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (1.995341ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.903174ms) +✔ 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.539121ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (67.503755ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.935878ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.781249ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (686.325791ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.664458ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.55206ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.004101ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.917914ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.947217ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.166081ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.283228ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.817515ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.375665ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (32.809267ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.87844ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.242295ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.642743ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.918933ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.396372ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1009.836205ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.463842ms) +✔ 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.047968ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.381771ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.220929ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.616961ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.676761ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.314775ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.842744ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.136825ms) +✔ tools: listing and search caps hold (12.308106ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.928262ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (6.670569ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.362704ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.267139ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.376714ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.11512ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.90241ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.912301ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.986547ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1027.299401ms) +✔ 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.237208ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.230824ms) +✔ 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.66606ms) +✔ 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.241633ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2848.156584 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:295:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.942977ms) + AssertionError [ERR_ASSERTION]: Missing expected exception: at: {"at":"2026-10-08","kind":"dm","decision":"a","outcome":"confirmed","messageId":"1"} + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:322:12) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-J4b.txt b/agents/rocko/work/s4-follow-up/out/mut-J4b.txt new file mode 100644 index 00000000..6c6494f2 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-J4b.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (31.641462ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (35.322452ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (31.007569ms) +✔ decide prints a declining choice as declining (21.334099ms) +✔ an unknown outcome is reported once and never resent (21.545933ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.821501ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (14.401704ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.161541ms) +✔ every human command refuses inside an agent run before it touches the bus (16.988672ms) +✔ usage errors exit 4; no business and no host is a usage error (22.531163ms) +✔ agents and tasks print through the broker (15.933678ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.287977ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (88.236762ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (61.977269ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (63.082225ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (60.535926ms) +✔ a business without tracker.baseUrl gets no trackers entry (55.16602ms) +✔ an unknown business and a broken system config refuse with exit 3 (76.783314ms) +✔ empty views say so (1.064505ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.308551ms) +✔ tasks print the tracker fields the snapshot carries (0.234359ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (972.034043ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (154.350102ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (74.920176ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.347216ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.739385ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.308466ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.124867ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.263685ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (208.216742ms) +✔ bus start refuses with exit 3 without a notifier config (89.979963ms) +✔ bus start runs until bus stop; status reports it while it runs (592.879248ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.965347ms) +✔ zoned uses the IANA zone across DST (26.816491ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (33.216071ms) +✔ two blocking decisions get two DMs with different nonces (20.697551ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.332262ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (17.676761ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (28.422641ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.570644ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.535023ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (22.602597ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (17.347517ms) +✔ an inbox read failure is logged and the next poll retries (1.200189ms) +✔ no Discord id reaches the journal or the log (22.245451ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (3.47286ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (4.677474ms) +✔ the journal: a whole file that is one torn line truncates to empty (3.288916ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (3.667165ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.338822ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (3.152447ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.646042ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.796723ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.755944ms) +✔ digest content stays within Discord's 2000 characters (0.482202ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.437991ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (364.361766ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (65.680839ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2264.835281ms) +✔ busExit and refuseInsideAgent (0.399536ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.393959ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.488072ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.568962ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.535135ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (20.721229ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (10.458788ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.96672ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.4541ms) +✔ authorize: open channel, listed user (1.857656ms) +✔ authorize: wrong guild (0.202587ms) +✔ authorize: no guild (DM) (0.185227ms) +✔ authorize: unlisted channel (0.19674ms) +✔ authorize: unknown channel, no info (0.215987ms) +✔ authorize: thread of listed parent (0.212618ms) +✔ authorize: thread of unlisted parent (0.161533ms) +✔ authorize: text channel that is not a thread and not listed (0.15495ms) +✔ authorize: unlisted user (0.200742ms) +✔ authorize: no author (0.293668ms) +✔ authorize: bot author (listed id, bot flag) (0.188008ms) +✔ authorize: system author (0.118566ms) +✔ authorize: the bot itself (0.080874ms) +✔ authorize: webhook (0.086295ms) +✔ authorize: mention channel without mention (0.205867ms) +✔ authorize: mention channel with bot mention (0.157022ms) +✔ authorize: mention channel with @everyone only (0.109362ms) +✔ authorize: mention channel mentioning someone else (0.08095ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.090266ms) +✔ authorize: private thread under mention channel, mentioned (0.083992ms) +✔ authorize: private thread under mention channel, not mentioned (0.071717ms) +✔ authorize: thread in another guild per channel info (0.101541ms) +✔ authorize: not an object (0.074541ms) +✔ authorize: no id (0.068059ms) +✔ authorize: oversize content is accepted and flagged (0.074999ms) +✔ authorize: exactly the limit is not oversize (0.068581ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.488922ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (1.190475ms) +✔ binding: a complete binding validates and is frozen (2.674013ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.358442ms) +✔ binding: empty allowlists refuse (0.4153ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.315184ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.778353ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.486844ms) +✔ binding: file must be 0600, regular, not a symlink (1.869814ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.381587ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (112.588398ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.510588ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (406.492464ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (203.097235ms) +✔ cli: run refuses when STOP is present, before any network use (127.324494ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.569021ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.074049ms) +✔ 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.740245ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (20.053806ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.60393ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.201759ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.276822ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.688447ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.542332ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.567071ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.191369ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.397294ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.975367ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.925905ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.263204ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.741574ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.085021ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.703092ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.005378ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.121373ms) +✔ 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 (7.4934ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.52353ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.915792ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.909654ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.244093ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.484088ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.853441ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.819341ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.347953ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.535876ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.519688ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.623174ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.292646ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.350414ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (56.673591ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (57.934364ms) +✔ engine: one prompt, one turn, text and usage come back (48.207752ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (353.752922ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (234.938963ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.384991ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.174488ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.644239ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (211.613099ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.356988ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.271412ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.50658ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (433.700172ms) +✔ engine: a malformed JSONL line fails the turn, not the process (30.309757ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.111341ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.820168ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.662312ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.568208ms) +✔ gateway: op 9 resumable resumes (0.485667ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.695931ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.545155ms) +✔ gateway: close() is final and unparseable frames are ignored (0.511481ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (87.685511ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (68.447224ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (127.464049ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.464149ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (88.244813ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (92.170311ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (99.858961ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (203.808709ms) +✔ git: push pushes the named branch only and reports up to date (64.694759ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (77.536586ms) +✔ git: the credential helper answers get over https from a private file and nothing else (211.623088ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (772.562388ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (6.015982ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (91.888288ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.051467ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.694244ms) +✔ 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.516666ms) +✔ 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 (319.75231ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.445221ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.608545ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.265128ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (36.49031ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (133.385729ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (84.369184ms) +✔ notices: a kind is recorded per UTC day and found again (0.41669ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.12683ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.021367ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.823894ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (60.813973ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (50.338857ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (32.858718ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (72.509993ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (651.879335ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (3.520304ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.082983ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.693962ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.769472ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.871511ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.356801ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.562355ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (3.569983ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.906368ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.333769ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.978455ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.027036ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.495905ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.738514ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.441111ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.290604ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.457839ms) +✔ 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.041447ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.271969ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199875ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.853766ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.072384ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.423642ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.821114ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.777509ms) +✔ tools: listing and search caps hold (11.031892ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.690283ms) +✔ 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 (3.937534ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (0.984937ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.934924ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.405574ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.140078ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.770148ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.524403ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.295062ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1022.016237ms) +✔ 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.900783ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.237087ms) +✔ 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.850069ms) +✔ 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.257061ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2801.746065 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:295:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (3.152447ms) + AssertionError [ERR_ASSERTION]: Missing expected exception: decision: {"at":"2026-10-08T12:00:00.000Z","kind":"dm","decision":7,"outcome":"confirmed","messageId":"1"} + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:322:12) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-J4c.txt b/agents/rocko/work/s4-follow-up/out/mut-J4c.txt new file mode 100644 index 00000000..a43d5296 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-J4c.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (27.643591ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (35.519263ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.51663ms) +✔ decide prints a declining choice as declining (17.103225ms) +✔ an unknown outcome is reported once and never resent (21.013534ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.771698ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.664329ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (22.91903ms) +✔ every human command refuses inside an agent run before it touches the bus (16.771711ms) +✔ usage errors exit 4; no business and no host is a usage error (28.335192ms) +✔ agents and tasks print through the broker (18.980555ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.40751ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (78.745402ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (55.791893ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (65.608092ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (56.192209ms) +✔ a business without tracker.baseUrl gets no trackers entry (63.695722ms) +✔ an unknown business and a broken system config refuse with exit 3 (84.188839ms) +✔ empty views say so (1.211604ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.614115ms) +✔ tasks print the tracker fields the snapshot carries (0.263092ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (963.651705ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (188.927523ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (86.130295ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (160.578811ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (142.029051ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (81.203624ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.75571ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.916303ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (228.047912ms) +✔ bus start refuses with exit 3 without a notifier config (101.463668ms) +✔ bus start runs until bus stop; status reports it while it runs (651.122298ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.989433ms) +✔ zoned uses the IANA zone across DST (29.022051ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (26.16599ms) +✔ two blocking decisions get two DMs with different nonces (23.329429ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.737087ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.42865ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (19.938613ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (34.258521ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (26.749293ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (21.13039ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (23.302223ms) +✔ an inbox read failure is logged and the next poll retries (1.334876ms) +✔ no Discord id reaches the journal or the log (21.74432ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (9.869971ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (8.396279ms) +✔ the journal: a whole file that is one torn line truncates to empty (3.039484ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.652043ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.291647ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (6.742891ms) +✔ the journal: a symlinked directory refuses and says it is a link (1.008499ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.951665ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.904542ms) +✔ digest content stays within Discord's 2000 characters (0.500688ms) +✔ runLoop never overlaps ticks and stops after the one in flight (113.599001ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (376.710441ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (60.889333ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2272.007535ms) +✔ busExit and refuseInsideAgent (0.93688ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.984573ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.649396ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.650683ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.519943ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (15.713554ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.009811ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.988703ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (4.054012ms) +✔ authorize: open channel, listed user (2.155137ms) +✔ authorize: wrong guild (0.231068ms) +✔ authorize: no guild (DM) (0.139169ms) +✔ authorize: unlisted channel (0.242635ms) +✔ authorize: unknown channel, no info (0.197047ms) +✔ authorize: thread of listed parent (0.217685ms) +✔ authorize: thread of unlisted parent (0.138087ms) +✔ authorize: text channel that is not a thread and not listed (0.143813ms) +✔ authorize: unlisted user (0.20649ms) +✔ authorize: no author (0.826126ms) +✔ authorize: bot author (listed id, bot flag) (0.147938ms) +✔ authorize: system author (0.134168ms) +✔ authorize: the bot itself (0.086033ms) +✔ authorize: webhook (0.079787ms) +✔ authorize: mention channel without mention (0.129088ms) +✔ authorize: mention channel with bot mention (0.124423ms) +✔ authorize: mention channel with @everyone only (0.102813ms) +✔ authorize: mention channel mentioning someone else (0.079671ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.101451ms) +✔ authorize: private thread under mention channel, mentioned (0.089304ms) +✔ authorize: private thread under mention channel, not mentioned (0.072609ms) +✔ authorize: thread in another guild per channel info (0.069493ms) +✔ authorize: not an object (0.064169ms) +✔ authorize: no id (0.086729ms) +✔ authorize: oversize content is accepted and flagged (0.078644ms) +✔ authorize: exactly the limit is not oversize (0.064843ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.492882ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.176708ms) +✔ binding: a complete binding validates and is frozen (2.701957ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.513197ms) +✔ binding: empty allowlists refuse (0.484505ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.372818ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.673879ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.508837ms) +✔ binding: file must be 0600, regular, not a symlink (1.759711ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.580571ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (112.96298ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.045177ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (386.063224ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (200.269955ms) +✔ cli: run refuses when STOP is present, before any network use (141.821303ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.85183ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.273087ms) +✔ 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 (17.936257ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.605297ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.640247ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.160227ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.339179ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.703086ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.822871ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.942841ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.555991ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.09954ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.017815ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.862744ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (10.393392ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.13336ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.983138ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.31752ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.062855ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.790629ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.077898ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.489226ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.657756ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.931782ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.347332ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.829283ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.855326ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.966854ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.333221ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (2.05521ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.324838ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.725719ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.633729ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.454609ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (59.129019ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (49.415781ms) +✔ engine: one prompt, one turn, text and usage come back (51.220156ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (364.961065ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.955945ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (116.019705ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.352138ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (137.680452ms) +✔ 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.351143ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.062479ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.451869ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.511624ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.918503ms) +✔ engine: a malformed JSONL line fails the turn, not the process (22.664666ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (42.859203ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.655333ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.717421ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.475124ms) +✔ gateway: op 9 resumable resumes (0.345225ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.883072ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.652498ms) +✔ gateway: close() is final and unparseable frames are ignored (0.446444ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.172652ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (62.074807ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (130.4647ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.472125ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (98.464789ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (82.906024ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.873477ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (207.426247ms) +✔ git: push pushes the named branch only and reports up to date (74.484382ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (94.567199ms) +✔ git: the credential helper answers get over https from a private file and nothing else (233.988825ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (920.904415ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.631822ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (88.588384ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.938697ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.429738ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (66.058687ms) +✔ 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 (331.769924ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.433509ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.171468ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.020419ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (35.622349ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (145.258571ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (89.71827ms) +✔ notices: a kind is recorded per UTC day and found again (0.523241ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.955312ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.861113ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.660167ms) +✔ 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.431183ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (56.072959ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (45.338491ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.074753ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (668.03626ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.51816ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.520593ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.952685ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.892951ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.901009ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.454531ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.51003ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.742372ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.50826ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (40.658199ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.329824ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.646494ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.33795ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (1.132372ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.363517ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.94769ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.5173ms) +✔ 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.33134ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.461949ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.245936ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.822686ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.007203ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.299205ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.631266ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (6.445715ms) +✔ tools: listing and search caps hold (12.040382ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.773293ms) +✔ 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.142623ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.015887ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.006631ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.383525ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.708757ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.41607ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.707807ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.884602ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1026.144482ms) +✔ 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.910791ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.276005ms) +✔ 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.00704ms) +✔ 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.699595ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3000.276564 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:295:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (6.742891ms) + AssertionError [ERR_ASSERTION]: Missing expected exception: status: {"at":"2026-10-08T12:00:00.000Z","kind":"dm","decision":"a","outcome":"refused","messageId":null,"status":"403"} + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:322:12) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-J4d.txt b/agents/rocko/work/s4-follow-up/out/mut-J4d.txt new file mode 100644 index 00000000..454c742a --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-J4d.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (41.966803ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.097449ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (23.69498ms) +✔ decide prints a declining choice as declining (21.07254ms) +✔ an unknown outcome is reported once and never resent (17.391697ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.608125ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.572481ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (18.891247ms) +✔ every human command refuses inside an agent run before it touches the bus (20.775923ms) +✔ usage errors exit 4; no business and no host is a usage error (28.627978ms) +✔ agents and tasks print through the broker (21.777188ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.814134ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (80.074717ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (62.005354ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.449612ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (64.644778ms) +✔ a business without tracker.baseUrl gets no trackers entry (65.718177ms) +✔ an unknown business and a broken system config refuse with exit 3 (82.718237ms) +✔ empty views say so (1.262534ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.697088ms) +✔ tasks print the tracker fields the snapshot carries (0.244247ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (971.845944ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (151.88012ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (84.294917ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (134.927199ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (116.886659ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (91.204963ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.252358ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.09451ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (227.24473ms) +✔ bus start refuses with exit 3 without a notifier config (115.300034ms) +✔ bus start runs until bus stop; status reports it while it runs (714.675186ms) +✔ bus-service.sh renders the unit and installs it into a given directory (35.377467ms) +✔ zoned uses the IANA zone across DST (30.869939ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (44.459735ms) +✔ two blocking decisions get two DMs with different nonces (24.492749ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.436265ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.309484ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (33.524709ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (26.55875ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.836076ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (21.282863ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (22.059919ms) +✔ an inbox read failure is logged and the next poll retries (2.068053ms) +✔ no Discord id reaches the journal or the log (25.647907ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (4.414229ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (4.682502ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.791605ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.079003ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.30314ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (4.707985ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.740859ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.766019ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.718589ms) +✔ digest content stays within Discord's 2000 characters (0.496017ms) +✔ runLoop never overlaps ticks and stops after the one in flight (116.482105ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (389.492252ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (79.33565ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2270.05979ms) +✔ busExit and refuseInsideAgent (0.584772ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.211031ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.53466ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.573472ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.625536ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (20.060861ms) +✔ 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.376565ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.355832ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.240272ms) +✔ authorize: open channel, listed user (6.518614ms) +✔ authorize: wrong guild (0.26889ms) +✔ authorize: no guild (DM) (0.160209ms) +✔ authorize: unlisted channel (0.182076ms) +✔ authorize: unknown channel, no info (0.464429ms) +✔ authorize: thread of listed parent (0.200252ms) +✔ authorize: thread of unlisted parent (0.172028ms) +✔ authorize: text channel that is not a thread and not listed (0.124469ms) +✔ authorize: unlisted user (0.185508ms) +✔ authorize: no author (1.217413ms) +✔ authorize: bot author (listed id, bot flag) (0.124484ms) +✔ authorize: system author (0.138957ms) +✔ authorize: the bot itself (0.073761ms) +✔ authorize: webhook (0.089308ms) +✔ authorize: mention channel without mention (0.152009ms) +✔ authorize: mention channel with bot mention (0.123676ms) +✔ authorize: mention channel with @everyone only (0.092072ms) +✔ authorize: mention channel mentioning someone else (0.082584ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.093984ms) +✔ authorize: private thread under mention channel, mentioned (0.07809ms) +✔ authorize: private thread under mention channel, not mentioned (0.073531ms) +✔ authorize: thread in another guild per channel info (0.065903ms) +✔ authorize: not an object (0.05368ms) +✔ authorize: no id (0.054333ms) +✔ authorize: oversize content is accepted and flagged (0.063206ms) +✔ authorize: exactly the limit is not oversize (0.055769ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.436373ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.166861ms) +✔ binding: a complete binding validates and is frozen (2.847364ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.240996ms) +✔ binding: empty allowlists refuse (0.428035ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.336318ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.709714ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.447635ms) +✔ binding: file must be 0600, regular, not a symlink (2.787058ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.553854ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (115.012865ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.270197ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (398.740447ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (209.363159ms) +✔ cli: run refuses when STOP is present, before any network use (133.32321ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.970279ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.199173ms) +✔ 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 (17.818489ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (20.098166ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.659682ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.16753ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.546124ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.975849ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.904314ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.539357ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.090762ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.016259ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.345485ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.236127ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.126717ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.890159ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.169082ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.041275ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.763057ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.089434ms) +✔ 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.471961ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (4.31072ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.865553ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.116829ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.709207ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.081103ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.32134ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.175487ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.316526ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.511178ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.317863ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.88573ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.299989ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.427714ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (58.111779ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (54.180505ms) +✔ engine: one prompt, one turn, text and usage come back (46.375444ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (365.133612ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.267836ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.184309ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.045554ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (133.059654ms) +✔ 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.425752ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.357728ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.820801ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.599095ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (433.072474ms) +✔ engine: a malformed JSONL line fails the turn, not the process (37.019414ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (56.099169ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.571084ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.968993ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.583697ms) +✔ gateway: op 9 resumable resumes (0.389204ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.79605ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.535765ms) +✔ gateway: close() is final and unparseable frames are ignored (0.395453ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (81.252516ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (60.05454ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (135.547057ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.348609ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (99.611908ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (101.195638ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (84.298307ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (213.017412ms) +✔ git: push pushes the named branch only and reports up to date (75.766287ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (85.340594ms) +✔ git: the credential helper answers get over https from a private file and nothing else (206.585292ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (816.130677ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.775587ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (94.037882ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (3.319636ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.789482ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (64.039427ms) +✔ 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 (338.130452ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.442039ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.721803ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.07883ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (62.099782ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (134.551297ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (96.737907ms) +✔ notices: a kind is recorded per UTC day and found again (0.416317ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.774252ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.022207ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.929507ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (55.52031ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (52.833927ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.815091ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (79.887662ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (679.7069ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.800138ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.247609ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.649344ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.851987ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.717194ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.681539ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.566101ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.905946ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.709423ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.140921ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.372659ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.859757ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.627391ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.940787ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.012968ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1017.594153ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.433237ms) +✔ 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.168368ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.267652ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.200275ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.327958ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.087159ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.190978ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.854966ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.773636ms) +✔ tools: listing and search caps hold (12.570471ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.938293ms) +✔ 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.871978ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (0.948924ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.016527ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.953793ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.180679ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.766959ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.858203ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.411369ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1028.884072ms) +✔ 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.128329ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.221509ms) +✔ 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.872481ms) +✔ 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.204624ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3014.938113 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:295:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (4.707985ms) + AssertionError [ERR_ASSERTION]: Missing expected exception: messageId: {"at":"2026-10-08T12:00:00.000Z","kind":"dm","decision":"a","outcome":"confirmed","messageId":null} + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:322:12) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-M28.txt b/agents/rocko/work/s4-follow-up/out/mut-M28.txt new file mode 100644 index 00000000..bb8f59b6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-M28.txt @@ -0,0 +1,275 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (31.719722ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (36.824606ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (22.767644ms) +✔ decide prints a declining choice as declining (24.668827ms) +✔ an unknown outcome is reported once and never resent (18.919231ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.463344ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.329446ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (18.000094ms) +✔ every human command refuses inside an agent run before it touches the bus (27.498598ms) +✔ usage errors exit 4; no business and no host is a usage error (22.685852ms) +✔ agents and tasks print through the broker (20.472104ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.571583ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (66.470735ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (53.998195ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (64.422993ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (66.323652ms) +✔ a business without tracker.baseUrl gets no trackers entry (62.833256ms) +✔ an unknown business and a broken system config refuse with exit 3 (94.449444ms) +✔ empty views say so (1.601906ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.358315ms) +✔ tasks print the tracker fields the snapshot carries (0.252744ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (993.133184ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (139.799718ms) +✖ a second host for the same data root refuses with exit 3 while the first runs (111.225187ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (138.505981ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (131.843098ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (74.560577ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.393583ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.767033ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (204.631487ms) +✔ bus start refuses with exit 3 without a notifier config (85.252005ms) +✔ bus start runs until bus stop; status reports it while it runs (596.559579ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.181696ms) +✔ zoned uses the IANA zone across DST (26.787292ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.307289ms) +✔ two blocking decisions get two DMs with different nonces (21.680364ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.424977ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.569248ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (31.884051ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (34.920697ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.706367ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (24.598025ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (16.546414ms) +✔ an inbox read failure is logged and the next poll retries (1.643479ms) +✔ no Discord id reaches the journal or the log (26.867815ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (5.77937ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (8.132901ms) +✔ the journal: a whole file that is one torn line truncates to empty (4.526598ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.115156ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.919761ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (7.028019ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.79119ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.869087ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.798291ms) +✔ digest content stays within Discord's 2000 characters (0.46731ms) +✔ runLoop never overlaps ticks and stops after the one in flight (113.094878ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (385.74549ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (67.453351ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2276.792937ms) +✔ busExit and refuseInsideAgent (0.395811ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.193125ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.590134ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.623601ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.535692ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (15.428031ms) +✔ 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.567928ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.779783ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (5.858597ms) +✔ authorize: open channel, listed user (2.016946ms) +✔ authorize: wrong guild (0.29867ms) +✔ authorize: no guild (DM) (0.197983ms) +✔ authorize: unlisted channel (0.184622ms) +✔ authorize: unknown channel, no info (0.163233ms) +✔ authorize: thread of listed parent (0.191943ms) +✔ authorize: thread of unlisted parent (0.257525ms) +✔ authorize: text channel that is not a thread and not listed (0.192846ms) +✔ authorize: unlisted user (0.290214ms) +✔ authorize: no author (0.308068ms) +✔ authorize: bot author (listed id, bot flag) (0.219737ms) +✔ authorize: system author (0.138269ms) +✔ authorize: the bot itself (0.102473ms) +✔ authorize: webhook (0.120127ms) +✔ authorize: mention channel without mention (0.343041ms) +✔ authorize: mention channel with bot mention (0.17213ms) +✔ authorize: mention channel with @everyone only (0.099901ms) +✔ authorize: mention channel mentioning someone else (0.072357ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.078214ms) +✔ authorize: private thread under mention channel, mentioned (0.083573ms) +✔ authorize: private thread under mention channel, not mentioned (0.07436ms) +✔ authorize: thread in another guild per channel info (0.074336ms) +✔ authorize: not an object (0.068171ms) +✔ authorize: no id (0.067967ms) +✔ authorize: oversize content is accepted and flagged (0.078013ms) +✔ authorize: exactly the limit is not oversize (2.234707ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.618829ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.150673ms) +✔ binding: a complete binding validates and is frozen (3.825709ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.335037ms) +✔ binding: empty allowlists refuse (0.426495ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.459532ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (3.260378ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.641519ms) +✔ binding: file must be 0600, regular, not a symlink (1.637434ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.330838ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (126.67327ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.798325ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (409.620358ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (191.885328ms) +✔ cli: run refuses when STOP is present, before any network use (135.510858ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.732445ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.217294ms) +✔ 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.305485ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.38883ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.616295ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.21026ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.352481ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.13292ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.455619ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.960828ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.220106ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.131612ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.27868ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.384625ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.529987ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (6.924194ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.611438ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.023079ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.361161ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.854582ms) +✔ 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.668091ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.713151ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.843672ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.636779ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.733655ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.299411ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.844904ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.505172ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.500455ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.949362ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.254604ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.830137ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.128729ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.459241ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (62.630797ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (50.877733ms) +✔ engine: one prompt, one turn, text and usage come back (50.957206ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (382.511497ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (229.850841ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (115.436713ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (225.535074ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (123.495707ms) +✔ 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.181452ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.412657ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.346262ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.501651ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.089234ms) +✔ engine: a malformed JSONL line fails the turn, not the process (28.512319ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.183127ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.899501ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.796397ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.5165ms) +✔ gateway: op 9 resumable resumes (0.360281ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (2.607398ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.588405ms) +✔ gateway: close() is final and unparseable frames are ignored (0.525236ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (78.776187ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (82.312067ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (143.195579ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.344564ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (84.006943ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (88.365912ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (88.685186ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (203.557303ms) +✔ git: push pushes the named branch only and reports up to date (70.715091ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (90.189485ms) +✔ git: the credential helper answers get over https from a private file and nothing else (205.965036ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (750.348411ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.062727ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (93.365592ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.15487ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.338151ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (73.059484ms) +✔ 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 (359.510661ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.441441ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.468164ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.05492ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (37.2506ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (140.914302ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (93.001566ms) +✔ notices: a kind is recorded per UTC day and found again (0.461268ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.125916ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.517775ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.349255ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (54.030277ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (76.603912ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (48.691131ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (69.559903ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (677.891149ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.775261ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.536385ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.649066ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.9198ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.623488ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (8.179491ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.784261ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.231102ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.557681ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.3176ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.543188ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.137416ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.128703ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.730113ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.363505ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.162646ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.435139ms) +✔ 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.05067ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.289677ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.213853ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.306527ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.425677ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.137777ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.454504ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.649069ms) +✔ tools: listing and search caps hold (16.812451ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.003642ms) +✔ 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.770143ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (2.880692ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.383946ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.54899ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.665508ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.289948ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.663688ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.410641ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1031.730376ms) +✔ 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.362198ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.237061ms) +✔ 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.700723ms) +✔ 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.18647ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2879.095421 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:152:1 +✖ a second host for the same data root refuses with exit 3 while the first runs (111.225187ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + CliError: broker refused to start: startup-refused + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:162:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: CliError: broker refused to start: startup-refused + at startHost (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/host.mjs:128:11) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async waitForActual (node:assert:615:5) + at async strict.rejects (node:assert:738:25) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:162:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7), + operator: 'rejects', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-N2.txt b/agents/rocko/work/s4-follow-up/out/mut-N2.txt new file mode 100644 index 00000000..f04cd9a4 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-N2.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (32.497516ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (38.490636ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (22.348175ms) +✔ decide prints a declining choice as declining (25.112741ms) +✔ an unknown outcome is reported once and never resent (16.833861ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (18.720274ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (24.391965ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (20.968235ms) +✔ every human command refuses inside an agent run before it touches the bus (28.704553ms) +✔ usage errors exit 4; no business and no host is a usage error (25.449941ms) +✔ agents and tasks print through the broker (19.448441ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (4.766765ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (82.34974ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (62.294674ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (64.754545ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (65.063425ms) +✔ a business without tracker.baseUrl gets no trackers entry (65.586983ms) +✔ an unknown business and a broken system config refuse with exit 3 (83.279826ms) +✔ empty views say so (1.123361ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.401426ms) +✔ tasks print the tracker fields the snapshot carries (0.228532ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1005.096231ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (170.444587ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.564099ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (135.196089ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (135.385998ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (85.752433ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (20.791112ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.761499ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (215.712543ms) +✔ bus start refuses with exit 3 without a notifier config (97.574356ms) +✔ bus start runs until bus stop; status reports it while it runs (600.989063ms) +✔ bus-service.sh renders the unit and installs it into a given directory (29.199158ms) +✔ zoned uses the IANA zone across DST (29.133529ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.221362ms) +✔ two blocking decisions get two DMs with different nonces (19.755433ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.38697ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.035949ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (27.099832ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.639538ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.363766ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (26.599492ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (20.656503ms) +✔ an inbox read failure is logged and the next poll retries (1.349527ms) +✔ no Discord id reaches the journal or the log (23.631117ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (6.13041ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (4.916627ms) +✔ the journal: a whole file that is one torn line truncates to empty (4.805046ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.530562ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.03679ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (5.514615ms) +✖ the journal: a symlinked directory refuses and says it is a link (1.483327ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.045902ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.93ms) +✔ digest content stays within Discord's 2000 characters (0.485367ms) +✔ runLoop never overlaps ticks and stops after the one in flight (114.590631ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (391.986621ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (61.511217ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2280.842611ms) +✔ busExit and refuseInsideAgent (0.412574ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.392105ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.506385ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.552264ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.67579ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.072074ms) +✔ 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.042445ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.538362ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.283797ms) +✔ authorize: open channel, listed user (1.847162ms) +✔ authorize: wrong guild (0.218365ms) +✔ authorize: no guild (DM) (0.168432ms) +✔ authorize: unlisted channel (0.177956ms) +✔ authorize: unknown channel, no info (0.250932ms) +✔ authorize: thread of listed parent (0.173952ms) +✔ authorize: thread of unlisted parent (0.13666ms) +✔ authorize: text channel that is not a thread and not listed (0.119261ms) +✔ authorize: unlisted user (1.081999ms) +✔ authorize: no author (0.268925ms) +✔ authorize: bot author (listed id, bot flag) (0.143329ms) +✔ authorize: system author (0.11497ms) +✔ authorize: the bot itself (0.084262ms) +✔ authorize: webhook (0.820073ms) +✔ authorize: mention channel without mention (0.228981ms) +✔ authorize: mention channel with bot mention (0.177931ms) +✔ authorize: mention channel with @everyone only (0.131955ms) +✔ authorize: mention channel mentioning someone else (0.094613ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.0935ms) +✔ authorize: private thread under mention channel, mentioned (0.094602ms) +✔ authorize: private thread under mention channel, not mentioned (0.073194ms) +✔ authorize: thread in another guild per channel info (0.085739ms) +✔ authorize: not an object (0.067934ms) +✔ authorize: no id (0.061094ms) +✔ authorize: oversize content is accepted and flagged (0.067229ms) +✔ authorize: exactly the limit is not oversize (0.060583ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.472367ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.232552ms) +✔ binding: a complete binding validates and is frozen (3.994609ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.312814ms) +✔ binding: empty allowlists refuse (0.502973ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (2.147937ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.866292ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.485903ms) +✔ binding: file must be 0600, regular, not a symlink (2.473028ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.485027ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (121.040848ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.351775ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (401.592756ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (215.340496ms) +✔ cli: run refuses when STOP is present, before any network use (156.249019ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.735866ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.390694ms) +✔ 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 (22.052854ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.885672ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.695996ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.244777ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.60155ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.759148ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.157215ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.739007ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.393344ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.575209ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.946899ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.869704ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.898533ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (7.303815ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.358421ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.81992ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.336127ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.794696ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (3.816366ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.416879ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.680609ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.563811ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (8.724542ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.568491ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.794968ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.817309ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.237381ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.458214ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.217341ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.846261ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.761161ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.415329ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (60.717944ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (66.591593ms) +✔ engine: one prompt, one turn, text and usage come back (61.088315ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (373.386515ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.358721ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.70835ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.015204ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.701226ms) +✔ 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.205773ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.943876ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.676499ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.739784ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (431.500734ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.789849ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.123881ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.39225ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.770806ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.526266ms) +✔ gateway: op 9 resumable resumes (0.400527ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.187042ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.566444ms) +✔ gateway: close() is final and unparseable frames are ignored (0.389055ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (78.425922ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (73.233119ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.921767ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.458224ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (95.532088ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (92.877385ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (94.779166ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (223.826431ms) +✔ git: push pushes the named branch only and reports up to date (81.807641ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (97.250815ms) +✔ git: the credential helper answers get over https from a private file and nothing else (220.327915ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (778.460728ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.025221ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (103.652162ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.057759ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.155145ms) +✔ 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.549571ms) +✔ 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 (356.19483ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.431217ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.574638ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.023751ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (47.524864ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (156.33667ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (97.717546ms) +✔ notices: a kind is recorded per UTC day and found again (0.468234ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.166868ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.372897ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.206314ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (59.414739ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (58.956985ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (45.250912ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (75.615727ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (717.310307ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.464619ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.274342ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.695432ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.890491ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.868787ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.110535ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.340765ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.564966ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.705828ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (33.318628ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.70041ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.285182ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.005852ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.654759ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.920918ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.795295ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.516392ms) +✔ 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.841094ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.316684ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.224161ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.744763ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.75266ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.761228ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.252323ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (6.189425ms) +✔ tools: listing and search caps hold (13.027554ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.97548ms) +✔ 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 (15.022162ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.21086ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.150493ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.336441ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.766637ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.310026ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.594078ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.551847ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1021.874141ms) +✔ 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.101597ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.273801ms) +✔ 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.786524ms) +✔ 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.41871ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2907.988628 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:334:1 +✖ the journal: a symlinked directory refuses and says it is a link (1.483327ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:341:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-N4.txt b/agents/rocko/work/s4-follow-up/out/mut-N4.txt new file mode 100644 index 00000000..8f359c83 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-N4.txt @@ -0,0 +1,267 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (37.62348ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (36.330508ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.591937ms) +✔ decide prints a declining choice as declining (23.299535ms) +✔ an unknown outcome is reported once and never resent (18.967804ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.245994ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (20.615078ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (15.437281ms) +✔ every human command refuses inside an agent run before it touches the bus (17.523778ms) +✔ usage errors exit 4; no business and no host is a usage error (22.848108ms) +✔ agents and tasks print through the broker (19.133473ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.943469ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (96.799246ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.956337ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.801908ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.600581ms) +✔ a business without tracker.baseUrl gets no trackers entry (59.696237ms) +✔ an unknown business and a broken system config refuse with exit 3 (79.030136ms) +✔ empty views say so (1.11215ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (2.916989ms) +✔ tasks print the tracker fields the snapshot carries (0.265947ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (976.619208ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (151.092001ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (77.927265ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (133.724603ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.744114ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.12753ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.420373ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.241047ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (211.416291ms) +✔ bus start refuses with exit 3 without a notifier config (84.459427ms) +✔ bus start runs until bus stop; status reports it while it runs (594.299415ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.094002ms) +✔ zoned uses the IANA zone across DST (36.577938ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (27.687007ms) +✔ two blocking decisions get two DMs with different nonces (18.826189ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.310685ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.970691ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (25.109136ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.60365ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.134701ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (23.996036ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (18.358179ms) +✔ an inbox read failure is logged and the next poll retries (1.689308ms) +✔ no Discord id reaches the journal or the log (23.621239ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (4.47762ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (5.047736ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.882032ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (1.096862ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.014103ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.215527ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.71468ms) +✖ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.537015ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.864733ms) +✔ digest content stays within Discord's 2000 characters (0.502465ms) +✔ runLoop never overlaps ticks and stops after the one in flight (112.589099ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (376.374374ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (77.358525ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2263.519215ms) +✔ busExit and refuseInsideAgent (0.410452ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.516223ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.806412ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.678347ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.497458ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (20.764413ms) +✔ 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.697163ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.590229ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.884584ms) +✔ authorize: open channel, listed user (2.061523ms) +✔ authorize: wrong guild (0.225839ms) +✔ authorize: no guild (DM) (0.201114ms) +✔ authorize: unlisted channel (0.206065ms) +✔ authorize: unknown channel, no info (0.371693ms) +✔ authorize: thread of listed parent (0.235539ms) +✔ authorize: thread of unlisted parent (0.170268ms) +✔ authorize: text channel that is not a thread and not listed (0.173029ms) +✔ authorize: unlisted user (1.087572ms) +✔ authorize: no author (0.281563ms) +✔ authorize: bot author (listed id, bot flag) (0.142261ms) +✔ authorize: system author (0.129558ms) +✔ authorize: the bot itself (0.096026ms) +✔ authorize: webhook (0.097146ms) +✔ authorize: mention channel without mention (0.151605ms) +✔ authorize: mention channel with bot mention (1.567799ms) +✔ authorize: mention channel with @everyone only (0.264562ms) +✔ authorize: mention channel mentioning someone else (0.095975ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.09995ms) +✔ authorize: private thread under mention channel, mentioned (0.087655ms) +✔ authorize: private thread under mention channel, not mentioned (0.079765ms) +✔ authorize: thread in another guild per channel info (0.081549ms) +✔ authorize: not an object (0.11792ms) +✔ authorize: no id (0.083436ms) +✔ authorize: oversize content is accepted and flagged (0.079163ms) +✔ authorize: exactly the limit is not oversize (0.109768ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.782771ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.179764ms) +✔ binding: a complete binding validates and is frozen (2.7305ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.487718ms) +✔ binding: empty allowlists refuse (0.401183ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (2.039909ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.860033ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.903012ms) +✔ binding: file must be 0600, regular, not a symlink (1.799112ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.566052ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (118.862501ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.728597ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (423.35948ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (193.752673ms) +✔ cli: run refuses when STOP is present, before any network use (139.745577ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.700128ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.444438ms) +✔ 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.494236ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.034495ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.618282ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.302365ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.375626ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.988243ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (31.984818ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (8.399466ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.533545ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.150002ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.352814ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.989386ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.480094ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.810415ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.110791ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.953426ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.886858ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.297211ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.774057ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.92225ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.115612ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.700801ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.222401ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.6176ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.907311ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.03687ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.248315ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.437978ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.250263ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.70542ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.513656ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.414881ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (58.452827ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (46.986646ms) +✔ engine: one prompt, one turn, text and usage come back (56.202887ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (353.391748ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (240.490058ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.348945ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (226.629586ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.826032ms) +✔ 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.677517ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.7834ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.26268ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.477141ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.808613ms) +✔ engine: a malformed JSONL line fails the turn, not the process (27.746039ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.038759ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.606578ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.484505ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.528046ms) +✔ gateway: op 9 resumable resumes (0.410331ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.817155ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.59244ms) +✔ gateway: close() is final and unparseable frames are ignored (0.502721ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (80.155593ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (65.030371ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (133.600924ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.297167ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (83.166698ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (122.379633ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (101.207743ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (205.456961ms) +✔ git: push pushes the named branch only and reports up to date (67.527591ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (84.631693ms) +✔ git: the credential helper answers get over https from a private file and nothing else (201.859418ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (742.544703ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.80267ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (105.394238ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.116147ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.894657ms) +✔ 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.339535ms) +✔ 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 (366.206588ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.456522ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (29.822214ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.018322ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (38.724707ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (147.26956ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (91.53557ms) +✔ notices: a kind is recorded per UTC day and found again (0.835869ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.027111ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.16864ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.055091ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (55.906163ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (53.68158ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (43.311643ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.98091ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (690.211689ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.520586ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.255302ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.225499ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.666303ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.750255ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.891019ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.554894ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.889594ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.771522ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.753054ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.601938ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.59235ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.046066ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.683679ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.245253ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.611258ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.490322ms) +✔ 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.494853ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.27141ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.197583ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.292227ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.39002ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.380898ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.672722ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.541939ms) +✔ tools: listing and search caps hold (11.574441ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.744493ms) +✔ 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.327012ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.465082ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.355536ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.911487ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.034439ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.851761ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.402914ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.2859ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1025.116536ms) +✔ 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.091156ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.21751ms) +✔ 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.574708ms) +✔ 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.640424ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2821.667216 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:345:1 +✖ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (1.537015ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:353:10) + at Test.runInAsyncScope (node:async_hooks:226:14) + at Test.run (node:internal/test_runner/test:1402:25) + at Test.processPendingSubtests (node:internal/test_runner/test:974:18) + at Test.postRun (node:internal/test_runner/test:1542:19) + at Test.run (node:internal/test_runner/test:1467:12) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: undefined, + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mut-N5.txt b/agents/rocko/work/s4-follow-up/out/mut-N5.txt new file mode 100644 index 00000000..35d5feaf --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mut-N5.txt @@ -0,0 +1,276 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (33.698397ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (36.841202ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (30.223846ms) +✔ decide prints a declining choice as declining (18.760355ms) +✔ an unknown outcome is reported once and never resent (17.149784ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.405241ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (16.059021ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.957888ms) +✔ every human command refuses inside an agent run before it touches the bus (18.658204ms) +✔ usage errors exit 4; no business and no host is a usage error (24.951316ms) +✔ agents and tasks print through the broker (14.561643ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.996325ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (78.049724ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (75.145974ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (58.085297ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (56.810686ms) +✔ a business without tracker.baseUrl gets no trackers entry (58.3762ms) +✔ an unknown business and a broken system config refuse with exit 3 (72.918546ms) +✔ empty views say so (1.855533ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (3.044737ms) +✔ tasks print the tracker fields the snapshot carries (0.264543ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (971.936253ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (151.966076ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (77.504917ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (127.321816ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (128.421661ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (79.102493ms) +✖ watchChildren reports a child that died before it was called, and one that dies later (24.138792ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.202369ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.502057ms) +✔ bus start refuses with exit 3 without a notifier config (89.201267ms) +✔ bus start runs until bus stop; status reports it while it runs (597.229223ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.007096ms) +✔ zoned uses the IANA zone across DST (32.200346ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (37.885759ms) +✔ two blocking decisions get two DMs with different nonces (20.714665ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.349474ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.344421ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (23.512152ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (33.230831ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.072905ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (21.590726ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (16.781686ms) +✔ an inbox read failure is logged and the next poll retries (1.204984ms) +✔ no Discord id reaches the journal or the log (24.213213ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (6.514027ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.648473ms) +✔ the journal: a whole file that is one torn line truncates to empty (6.572512ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.266636ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.08847ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.51419ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.624272ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.697156ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.712743ms) +✔ digest content stays within Discord's 2000 characters (0.539437ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.889547ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (371.03397ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (63.072985ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2249.669472ms) +✔ busExit and refuseInsideAgent (0.524671ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.496627ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.638591ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.605402ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.497791ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.786467ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (5.169753ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.224176ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.737755ms) +✔ authorize: open channel, listed user (2.570321ms) +✔ authorize: wrong guild (0.202796ms) +✔ authorize: no guild (DM) (0.16124ms) +✔ authorize: unlisted channel (0.17353ms) +✔ authorize: unknown channel, no info (0.15263ms) +✔ authorize: thread of listed parent (0.174787ms) +✔ authorize: thread of unlisted parent (0.139129ms) +✔ authorize: text channel that is not a thread and not listed (0.157015ms) +✔ authorize: unlisted user (0.305344ms) +✔ authorize: no author (0.293006ms) +✔ authorize: bot author (listed id, bot flag) (0.174231ms) +✔ authorize: system author (0.103008ms) +✔ authorize: the bot itself (0.105989ms) +✔ authorize: webhook (0.091704ms) +✔ authorize: mention channel without mention (0.148552ms) +✔ authorize: mention channel with bot mention (0.1454ms) +✔ authorize: mention channel with @everyone only (0.095023ms) +✔ authorize: mention channel mentioning someone else (0.072051ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.081847ms) +✔ authorize: private thread under mention channel, mentioned (0.126752ms) +✔ authorize: private thread under mention channel, not mentioned (0.05011ms) +✔ authorize: thread in another guild per channel info (0.069016ms) +✔ authorize: not an object (0.086579ms) +✔ authorize: no id (0.076186ms) +✔ authorize: oversize content is accepted and flagged (0.093252ms) +✔ authorize: exactly the limit is not oversize (0.08954ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.522513ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.160767ms) +✔ binding: a complete binding validates and is frozen (3.141303ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.372644ms) +✔ binding: empty allowlists refuse (0.38767ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.348349ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.628404ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.492059ms) +✔ binding: file must be 0600, regular, not a symlink (1.485084ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.374793ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (113.203348ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.493414ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (372.512ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (190.096457ms) +✔ cli: run refuses when STOP is present, before any network use (130.711156ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.873882ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.239069ms) +✔ 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.10929ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.270969ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.516459ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.354129ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.370236ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.613709ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.117218ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.306551ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.258267ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.604637ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.000634ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.68049ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.532455ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.705783ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.892775ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.957931ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.168583ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.739148ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (4.068217ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (7.71617ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.561091ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.812384ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.16763ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (5.604324ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.887618ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.745169ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.173496ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.532875ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.349165ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.69471ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.791861ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.453761ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (63.66679ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (63.472529ms) +✔ engine: one prompt, one turn, text and usage come back (42.928123ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (354.43756ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.045881ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (114.797967ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.330023ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.011125ms) +✔ 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.44806ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.749173ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.34552ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.500854ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.023654ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.483632ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.478498ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.989541ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.748031ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.570047ms) +✔ gateway: op 9 resumable resumes (0.388531ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.819944ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.523874ms) +✔ gateway: close() is final and unparseable frames are ignored (0.498819ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.23357ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (62.835869ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.439208ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.446574ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (92.543917ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (84.475504ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (84.667593ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (194.07553ms) +✔ git: push pushes the named branch only and reports up to date (69.012642ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (88.604739ms) +✔ git: the credential helper answers get over https from a private file and nothing else (209.101826ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (765.044736ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.389801ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (89.907033ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.33035ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.600608ms) +✔ 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.296931ms) +✔ 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 (320.311854ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.37093ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.153703ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.033232ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (37.561655ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (129.223703ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (87.15181ms) +✔ notices: a kind is recorded per UTC day and found again (0.441976ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.782946ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.4048ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.788619ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (47.919227ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (52.521144ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.435694ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (78.720187ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (664.423592ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.696425ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (4.969241ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.746828ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.286921ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.907063ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.505028ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.840352ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.61937ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.090374ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.921152ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.595973ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.326657ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.873523ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.671494ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.574564ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.194318ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.44458ms) +✔ 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.085035ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.277589ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.195026ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.700804ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.72362ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.663037ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.9241ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.293996ms) +✔ tools: listing and search caps hold (10.530074ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.895669ms) +✔ 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.489273ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.284852ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.737902ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.967897ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.318822ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.885589ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.192573ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.367844ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1023.512721ms) +✔ 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.213528ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.242007ms) +✔ 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.720933ms) +✔ 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.410981ms) +ℹ tests 238 +ℹ suites 0 +ℹ pass 237 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2832.895398 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:232:1 +✖ watchChildren reports a child that died before it was called, and one that dies later (24.138792ms) + AssertionError [ERR_ASSERTION]: a death by signal before the watch is not lost either + + actual - expected + + + [] + - [ + - [ + - 'broker', + - null, + - 'SIGKILL' + - ] + - ] + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:241:10) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: [], + expected: [ [ 'broker', null, 'SIGKILL' ] ], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/mutants.txt b/agents/rocko/work/s4-follow-up/out/mutants.txt new file mode 100644 index 00000000..35620d0f --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/mutants.txt @@ -0,0 +1,18 @@ +N2 killed (fail 1, cancelled 0) ✖ the journal: a symlinked directory refuses and says it is a link; +N4 killed (fail 1, cancelled 0) ✖ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it; +N5 killed (fail 1, cancelled 0) ✖ watchChildren reports a child that died before it was called, and one that dies later; +M28 killed (fail 1, cancelled 0) ✖ a second host for the same data root refuses with exit 3 while the first runs; +G150 killed (fail 1, cancelled 0) ✖ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker; +G144 killed (fail 1, cancelled 0) ✖ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker; +F2a killed (fail 1, cancelled 0) ✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count; +F2b killed (fail 1, cancelled 0) ✖ 429s, 5xx-style unknowns and refusals without a status never count toward the limit; +F2c killed (fail 2, cancelled 0) ✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing;✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count; +F2d killed (fail 1, cancelled 0) ✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing; +F2e killed (fail 1, cancelled 0) ✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count; +F2f killed (fail 1, cancelled 0) ✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count; +J4a killed (fail 1, cancelled 0) ✖ the journal: a line with a wrong type refuses with exit 3 and names the field; +J4b killed (fail 1, cancelled 0) ✖ the journal: a line with a wrong type refuses with exit 3 and names the field; +J4c killed (fail 1, cancelled 0) ✖ the journal: a line with a wrong type refuses with exit 3 and names the field; +J4d killed (fail 1, cancelled 0) ✖ the journal: a line with a wrong type refuses with exit 3 and names the field; +E1 killed (fail 1, cancelled 0) ✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path; +E2 killed (fail 1, cancelled 0) ✖ the journal: a symlinked directory refuses and says it is a link; diff --git a/agents/rocko/work/s4-follow-up/out/node-bus.txt b/agents/rocko/work/s4-follow-up/out/node-bus.txt new file mode 100644 index 00000000..963817ba --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-bus.txt @@ -0,0 +1,75 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (26.846644ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (24.410589ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (27.415485ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (17.322612ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (15.214254ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (14.265276ms) +✔ task action subjects and linked decision trail are complete and ordered (19.069327ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (7.77888ms) +✔ business isolation includes inherited object names and cross-business message references (14.751071ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (18.821953ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (9.51298ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (16.665953ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (10.381636ms) +✔ both arbiters require human resolution when their cross-role route is themselves (16.060798ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.260303ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.662176ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.274841ms) +✔ expiry refuses use and env references never become client data (0.846181ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.886071ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.380255ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (3.142096ms) +✔ process reader gets own kernel identity without exposing environment values (0.587722ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.90547ms) +✔ real pid 1 remains inspectable when its environment is protected (0.332779ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (26.991803ms) +✔ missing and foreign-business citations refuse and roll back message and grant (17.104311ms) +✔ cross-role sends still need a matching resolved decision and consume it once (24.686477ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (71.175386ms) +✔ startup token refusal returns safe code without value or partial listening broker (40.729917ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (54.183712ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (78.772711ms) +✔ trusted host registers later launches; socket clients never have a registration verb (45.867398ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (32.232364ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (48.104217ms) +✔ v3b prototype refusals, views and append-only mutations (81.348764ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (29.934609ms) +✔ another run cannot consume an approval; a failed check leaves it usable (20.555999ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (15.996108ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (28.92796ms) +✔ class drift gated to cross-role refuses before consumption (18.925789ms) +✔ class drift cross-role to gated refuses before consumption (15.067399ms) +✔ class drift gated to within-role refuses before consumption (17.375127ms) +✔ class drift cross-role to within-role refuses before consumption (15.892017ms) +✔ class drift within-role to gated refuses before consumption (14.067652ms) +✔ class drift within-role to cross-role refuses before consumption (14.605399ms) +✔ message.send consumes approval and prevents a later send or authorize (16.732989ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (17.644261ms) +✔ creates private WAL store and excludes a second writer until explicit close (16.034473ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (17.001735ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (16.692012ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (45.916215ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (9.472638ms) +✔ async transactions refuse before invoking their function (6.980829ms) +✔ recordTask keeps sync reads and a role write apart (20.710101ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (10.109562ms) +✔ a bad entry refuses the whole record (10.378294ms) +✔ taskView reads the projection for one business (13.335375ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (22.935167ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (275.637672ms) +✔ without an adapter the server refuses every task verb (16.580658ms) +✔ the runtime refuses an invalid adapter and closes a valid one (15.966457ms) +✔ the process loads the S3 adapter from plain-data trackers (93.982671ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (27.361245ms) +✔ two wire claims serialize; a lost reply never automatically retries (18.975306ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (11.699018ms) +✔ client preserves UTF-8 when a response divides a multibyte character (10.659184ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (14.223015ms) +ℹ tests 67 +ℹ suites 0 +ℹ pass 67 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 589.325424 diff --git a/agents/rocko/work/s4-follow-up/out/node-business.txt b/agents/rocko/work/s4-follow-up/out/node-business.txt new file mode 100644 index 00000000..32bd4b96 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-business.txt @@ -0,0 +1,68 @@ +✔ config directory and file path follow MOSAIC_CONFIG (2.060554ms) +✔ the fixture business validates and comes back frozen (8.891121ms) +✔ two instances may share a definition (1.615478ms) +✔ top-level refusals (10.211662ms) +✔ arbiters and projects (6.388944ms) +✔ role instances (6.610536ms) +✔ Vikunja bots (5.670745ms) +✔ a role without Vikunja takes no tracker block (1.943047ms) +✔ credential references match the definition's services (2.256351ms) +✔ launch (6.688948ms) +✔ loadBusiness: file checks (1.677377ms) +✔ loadBusiness: not a regular file (40.685368ms) +✔ loading writes nothing (1.331501ms) +✔ names that are Object.prototype properties don't count as declared (2.783854ms) +✔ the shipped example refuses as written and validates once filled in (0.624939ms) +✔ usage errors exit 4 (295.59984ms) +✔ validate: a good business exits 0 and prints instance digests (67.837139ms) +✔ validate: project files (318.727398ms) +✔ validate: missing files and a broken system config (233.964049ms) +✔ validate: credential reference problems exit 2 and name each one (69.456587ms) +✔ validate: a token file inside the repository is refused (72.059109ms) +✔ validate: role definitions come from MOSAIC_ROLES_DIR (202.693628ms) +✔ resolve: prints one instance's record (193.75122ms) +✔ resolve: refusals (413.820251ms) +✔ parse: exactly one of file or env, plus the service's date (3.146733ms) +✔ check: a good file has no problems (5.026882ms) +✔ check never opens the file: a write-only token passes (0.429986ms) +✔ check: file problems (1.106131ms) +✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.067741ms) +✔ check: dates and environment references (0.512385ms) +✔ path and load (3.014255ms) +✔ refusals (1.475493ms) +✔ systemVars flattens the validated config (2.391181ms) +✔ precedence: system, business, project, project role, agent (11.420701ms) +✔ limits narrow the definition and never widen it (3.142722ms) +✔ role.launch stays within-role only for the instance the launch block names (8.557034ms) +✔ limits.authority without role.launch leaves the launcher with no launch block (1.83253ms) +✔ limits.authority narrows cross-role actions too (0.973509ms) +✔ classify (1.081899ms) +✔ the record carries what the broker and launcher need (0.856808ms) +✔ digest: key order doesn't matter, any value change does (5.508374ms) +✔ refusals (2.123402ms) +✔ the four shipped version 2 roles load (2.591051ms) +✔ shipped role scopes match addendum B section 2 and the SR runbook (0.880223ms) +✔ shipped authority follows the note's table (0.41691ms) +✔ version 1 files keep loading with no authority (0.815806ms) +✔ the conductor policy isn't a role (0.203812ms) +✔ a missing role file is exit 4, a symbolic link too (0.343785ms) +✔ version 2 refusals (1.111426ms) +✔ authority: closed vocabulary, no gated-only action, no overlap (2.487968ms) +✔ credentials: Gitea scopes (1.060952ms) +✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.140756ms) +✔ credentials: services (4.276744ms) +✔ contract: a non-empty regular Markdown file beside the role file (0.628106ms) +✔ every key names known layers and a merge rule (0.751364ms) +✔ unknown keys and wrong layers refuse (0.685442ms) +✔ types (2.207498ms) +✔ merge: defaults, then the most specific layer wins (0.320241ms) +✔ merge: limits only narrow, and provenance lists each source (0.442349ms) +✔ merge doesn't change its inputs (0.191325ms) +ℹ tests 60 +ℹ suites 0 +ℹ pass 60 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 1939.00582 diff --git a/agents/rocko/work/s4-follow-up/out/node-cli.txt b/agents/rocko/work/s4-follow-up/out/node-cli.txt new file mode 100644 index 00000000..b32c364a --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-cli.txt @@ -0,0 +1,70 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (15.333744ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (19.761043ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (12.070932ms) +✔ decide prints a declining choice as declining (14.791719ms) +✔ an unknown outcome is reported once and never resent (9.838594ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (11.487185ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (10.776028ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (10.706493ms) +✔ every human command refuses inside an agent run before it touches the bus (10.87443ms) +✔ usage errors exit 4; no business and no host is a usage error (10.053197ms) +✔ agents and tasks print through the broker (9.736823ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.266942ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (38.056701ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (37.348149ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (33.621208ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (35.945482ms) +✔ a business without tracker.baseUrl gets no trackers entry (38.794152ms) +✔ an unknown business and a broken system config refuse with exit 3 (59.97719ms) +✔ empty views say so (0.668769ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.059245ms) +✔ tasks print the tracker fields the snapshot carries (0.196323ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (836.097185ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (143.632917ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (74.009683ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (133.931155ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (129.1185ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (71.509915ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (20.94978ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.12217ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (202.899358ms) +✔ bus start refuses with exit 3 without a notifier config (94.924396ms) +✔ bus start runs until bus stop; status reports it while it runs (602.666947ms) +✔ bus-service.sh renders the unit and installs it into a given directory (35.723488ms) +✔ zoned uses the IANA zone across DST (14.603844ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (16.983817ms) +✔ two blocking decisions get two DMs with different nonces (12.118336ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.180211ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (10.154915ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (15.424671ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (17.149256ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (11.014788ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (12.696482ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.612958ms) +✔ an inbox read failure is logged and the next poll retries (0.807809ms) +✔ no Discord id reaches the journal or the log (14.019126ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.32609ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.073151ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.609898ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.619298ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.627086ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.528865ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.877939ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.490916ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.491892ms) +✔ digest content stays within Discord's 2000 characters (0.261386ms) +✔ runLoop never overlaps ticks and stops after the one in flight (112.038558ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (256.738911ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (30.628962ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2158.215151ms) +✔ busExit and refuseInsideAgent (0.383363ms) +ℹ tests 60 +ℹ suites 0 +ℹ pass 60 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2648.404994 diff --git a/agents/rocko/work/s4-follow-up/out/node-control-board.txt b/agents/rocko/work/s4-follow-up/out/node-control-board.txt new file mode 100644 index 00000000..edcba466 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-control-board.txt @@ -0,0 +1,132 @@ +✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (3.457901ms) +✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.210965ms) +✔ completed smoke replies and ordinary questions are idle, not human blockers (1.090015ms) +✔ only an explicit first-line input request makes a finished reply waiting (0.20662ms) +✔ tool activity, user text, errors and unfinished turns override attention text (0.128362ms) +✔ STOP access failure is unknown, not absence, under a non-root identity (34.802356ms) +✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.350334ms) +✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.532551ms) +✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (6.321534ms) +✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.520501ms) +✔ server rescans connector discovery and refuses HTTP reply without transport (32.830631ms) +✔ connector session links and linked directories are not read (1.109034ms) +✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (3.017821ms) +✔ CLI print uses the relaunch notice instead of old current preview (54.849283ms) +✔ connector owner and fixed task never inherit a native relaunch notice (2.375021ms) +✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.041907ms) +✔ loadConfig: missing file throws ConfigError (1.40259ms) +✔ loadConfig: invalid JSON throws ConfigError (0.273634ms) +✔ loadConfig: missing dataRoot throws ConfigError (0.208746ms) +✔ loadConfig: relative dataRoot throws ConfigError (0.194581ms) +✔ loadConfig: valid config returns dataRoot (0.616688ms) +✔ findNewestSession: picks the newest by mtime among two files (0.4533ms) +✔ findNewestSession: finds files in nested subdirectories (0.294144ms) +✔ findNewestSession: returns null for a missing dir (0.117402ms) +✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.616183ms) +✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.143359ms) +✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.445338ms) +✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.023221ms) +✔ deriveState: full state table (0.161249ms) +✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.287196ms) +✔ rule: newest entry is a tool result with no assistant text after it is working (0.26169ms) +✔ rule: a finished ordinary turn is idle, even if it says your move (0.243287ms) +✔ task: the first user message of the session, from text blocks (0.239048ms) +✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.227896ms) +✔ task: no user message in the log means null (shown as unknown), never a guess (0.250213ms) +✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.346582ms) +✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.465724ms) +✔ scan: the written record carries task, workspace and activeProject (0.43714ms) +✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.538162ms) +✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.366495ms) +✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (0.92943ms) +✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.384102ms) +✔ loadRegistrations: a missing seatsDir gives empty lists (0.157807ms) +✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.676201ms) +✔ matchRegistration: matches by sessionsDir, and by realpath through a symlink; sessionsDir null never matches; same seat name with a different sessionsDir does not match (fleet vs repo darkwing) (0.301293ms) +✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.64436ms) +✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.767931ms) +✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.501275ms) +✔ scanAgent: ageSeconds is computed from the injected now (0.252702ms) +✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.145448ms) +✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.315535ms) +✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.501566ms) +✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.895728ms) +✔ scan: relative boardDir throws ConfigError (0.092901ms) +✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (53.690492ms) +✔ CLI: missing config exits 2 with a refused: message (51.39965ms) +✔ CLI: unknown command exits 2 (49.844823ms) +✔ CLI: unknown --liveness value exits 2 (47.375608ms) +✔ panesRunPi: true when any trimmed line equals 'pi' (0.179214ms) +✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.063738ms) +✔ tmuxIsAlive: a pane running pi is alive (0.156185ms) +✔ tmuxIsAlive: session exists but pi has exited is not alive (0.108197ms) +✔ tmuxIsAlive: no such tmux session is not alive (0.052732ms) +✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.060265ms) +✔ tmuxIsAlive: passes -L only when a socket is given (0.089151ms) +✔ parsePanes: one pane per line, command and optional tab-separated path (0.071838ms) +✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.07376ms) +✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.089511ms) +✔ loadSeen: missing file returns {} (0.12806ms) +✔ loadSeen: invalid JSON throws ConfigError (0.171159ms) +✔ loadSeen: a JSON array throws ConfigError (0.14116ms) +✔ loadSeen: a non-string value throws ConfigError (0.150134ms) +✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.318986ms) +✔ markSeen: seen false deletes the key (0.241303ms) +✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.184977ms) +✔ markSeen: project containing '/' throws ConfigError (0.118837ms) +✔ markSeen: non-boolean seen throws ConfigError (0.114032ms) +✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.268492ms) +✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.214726ms) +✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.218063ms) +✔ scanAgent: an error-state session with a matching mark is seen (0.195255ms) +✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.486331ms) +✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.172043ms) +✔ taskSetBy: a registered task carries the record's setter; a record without the field (pre-#1511) reads unknown; the value is not copied into registered (0.591856ms) +✔ taskSetBy: null whenever the task shown is not the registered one: no registration, an empty registered task, a stale registration; the field is always present (0.534497ms) +✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (0.941446ms) +✔ isLoopbackHost: recognizes loopback hosts (1.157069ms) +✔ isLoopbackHost: rejects non-loopback hosts (4.22175ms) +✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (2.882772ms) +✔ startServer: serves page, healthz, and a rescanning /api/board (33.329392ms) +✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (6.003934ms) +✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.17292ms) +✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (51.945036ms) +✔ CLI: serve rejects a non-numeric --port with exit 2 (47.723159ms) +✔ CLI: scan still works after the async cli refactor (51.151701ms) +✔ CLI: live serve prints its URL and answers /healthz (59.918321ms) +✔ page.html: esc() escapes every HTML-significant character (0.585672ms) +✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (7.934282ms) +✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.183499ms) +✔ POST /api/seen with invalid JSON returns 400 (2.802761ms) +✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (1.992493ms) +✔ POST /api/seen with a missing agent returns 400 (1.324636ms) +✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.617933ms) +✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (50.366821ms) +✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.302553ms) +✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.261579ms) +✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.159021ms) +✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.125061ms) +✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.315393ms) +✔ page.html: task and active project cells show their source via sourceTag(); the detail has a Registered row via registeredText(); SOURCE_LABEL maps registration to registered; every dynamic value in sourceTag/fromSource/registeredText is escaped (0.590488ms) +✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S :control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (29.211381ms) +✔ POST /api/reply: a registration with a tmux socket adds -L (24.744096ms) +✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (25.396115ms) +✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (13.805133ms) +✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.223438ms) +✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.151448ms) +✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (1.01866ms) +✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.373716ms) +✔ page.html: the task cell and detail show who set a registered task via setByTag()/setByText(), both escaped, only from rec.taskSetBy; the reply gate does not read it (0.306642ms) +✔ Host/Origin guard: GET /api/board and POST /api/reply refuse a foreign Host, a wrong port and a cross-origin Origin with 403 JSON, before any scan or send, and never send CORS headers (7.947275ms) +✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (28.619647ms) +✔ conversation routes (F16): a foreign Host, a wrong port and a cross-origin Origin get 403 before the reader runs, with no CORS headers (5.50907ms) +✔ every refusal code the reader can raise has an HTTP status (0.90892ms) +✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (47.93621ms) +ℹ tests 124 +ℹ suites 0 +ℹ pass 124 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 607.404619 diff --git a/agents/rocko/work/s4-follow-up/out/node-conversation.txt b/agents/rocko/work/s4-follow-up/out/node-conversation.txt new file mode 100644 index 00000000..7b0a0d62 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-conversation.txt @@ -0,0 +1,205 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (123.886324ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (7.755379ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (3.65401ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (162.486357ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (272.008644ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (194.387604ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (20.97984ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (20.016183ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.839286ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5534.169545ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (21929.381212ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (154.511521ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (91.583066ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (220.322777ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (192.230518ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (212.053095ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (33.238494ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (96.007412ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (32.189988ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (88.899598ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (23.44816ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (57.959617ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (51.394722ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.469485ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.099651ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.759782ms) +✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2638.995605ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (120.815989ms) +✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2145.378907ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4276.305903ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2150.599749ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2238.71035ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2197.403591ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4389.273083ms) +✖ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (220.95379ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (305.662874ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (282.798305ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (64.911446ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (30.698505ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (41.472205ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3026.575238ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (5.079035ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (29.75158ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (25.798198ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (35.971066ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (29.491638ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (23.236826ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (42.901852ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (14.268623ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.429962ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (17.207644ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (128.639854ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (42.234223ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (28.958148ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (37.213287ms) +✔ E4: a page read after message_end but before its entry is persisted: marker at the seam, re-read after run-settled, each message once (33.531476ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.822207ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (30.508636ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (49.795234ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (44.891328ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (27.506297ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.70488ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.472935ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.535934ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.205422ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (357.924335ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (49.509042ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (88.63243ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (147.250773ms) +✔ H4: self-takeover is refused (26.662033ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (114.174961ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (97.942599ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (27.642411ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (85.725198ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (132.18795ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (18.297678ms) +✔ H13: a retry with the same request ID and different text is refused (16.898281ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (136.257726ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (68.025803ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (21.620776ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (51.721388ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (52.616189ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (13.050317ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (118.05931ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.661981ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (476.876348ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (376.160543ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (521.879678ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2407.944298ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (488.034247ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.053202ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.692911ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.637046ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.520464ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.732369ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.923983ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.781628ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.230053ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.406218ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.175191ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.089202ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (4.765007ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (8.003776ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.186944ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (2.051135ms) +✔ F9: registrations never add or redirect a root (1.51796ms) +✔ F10: a header cwd naming another project is refused (3.391099ms) +✔ F11: parentSession renders with a marker and the parent is never opened (0.749559ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (6.036891ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.771538ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.429035ms) +✔ F13: compaction is a marker in place, then the retained content (0.789613ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (720.732626ms) +✔ fragments never cut a surrogate pair and keep an empty string (8.322828ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (2.705224ms) +✔ unknown conversations, empty files and non-Pi files refuse (3.996185ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.771588ms) +✔ a seat directory without search permission refuses that root, not the catalogue (3.562476ms) +✔ every page and cursor is a valid CHAT-01 record (860.131884ms) +✔ the engine pin holds for the installed package (1.755515ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (310.30598ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (388.783611ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (72.457016ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (58.388647ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (19.920269ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.000044ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (35.20039ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (22.838822ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (117.180697ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (64.482426ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1548.239144ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (15.635725ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (69.01426ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (14.559371ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (17.497072ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (31.712737ms) +✔ N10: fake conformance (32.449068ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (28.329983ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (19.351161ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (64.655033ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (31.417017ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (28.416734ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (23.332338ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (13.235099ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (30.043722ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (109.473027ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (133.702413ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (82.943002ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (15.065652ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (13.360129ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (18.561592ms) +✔ N24: the seal is an allow-list: --extension, a missing --no-* flag, a second --mode or --session, a session or output flag, or a stray word refuses unsealed-engine; no engine starts (37.711816ms) +ℹ tests 152 +ℹ suites 0 +ℹ pass 149 +ℹ fail 3 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 31544.713442 + +✖ failing tests: + +test at packages/conversation/tests/cohort.test.mjs:139:1 +✖ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2638.995605ms) + AssertionError [ERR_ASSERTION]: the escaped child is a listed member + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/conversation/tests/cohort.test.mjs:151:12) + at async Test.run (node:internal/test_runner/test:1409:7) + at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at packages/conversation/tests/cohort.test.mjs:176:1 +✖ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2145.378907ms) + AssertionError [ERR_ASSERTION]: the member ignored TERM + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/conversation/tests/cohort.test.mjs:190:12) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at packages/conversation/tests/cohort.test.mjs:426:3 +✖ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (220.95379ms) + AssertionError [ERR_ASSERTION]: the member is alive across the crash + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/conversation/tests/cohort.test.mjs:431:12) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: false, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/node-discord.txt b/agents/rocko/work/s4-follow-up/out/node-discord.txt new file mode 100644 index 00000000..5869e0b7 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-discord.txt @@ -0,0 +1,186 @@ +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.048365ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.509589ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.567279ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.427021ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.461407ms) +✔ approvals flow: a non-approver, a repeat, a wrong custom id and a service refusal each get their fixed line and a drop entry (7.718488ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.792264ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.974669ms) +✔ authorize: open channel, listed user (2.583069ms) +✔ authorize: wrong guild (0.195464ms) +✔ authorize: no guild (DM) (0.335835ms) +✔ authorize: unlisted channel (0.194541ms) +✔ authorize: unknown channel, no info (0.295776ms) +✔ authorize: thread of listed parent (0.187446ms) +✔ authorize: thread of unlisted parent (0.249749ms) +✔ authorize: text channel that is not a thread and not listed (0.14337ms) +✔ authorize: unlisted user (1.063207ms) +✔ authorize: no author (0.607158ms) +✔ authorize: bot author (listed id, bot flag) (0.141104ms) +✔ authorize: system author (0.129682ms) +✔ authorize: the bot itself (0.090794ms) +✔ authorize: webhook (0.109088ms) +✔ authorize: mention channel without mention (0.133929ms) +✔ authorize: mention channel with bot mention (1.156736ms) +✔ authorize: mention channel with @everyone only (0.219088ms) +✔ authorize: mention channel mentioning someone else (0.086046ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.080592ms) +✔ authorize: private thread under mention channel, mentioned (0.08324ms) +✔ authorize: private thread under mention channel, not mentioned (0.072543ms) +✔ authorize: thread in another guild per channel info (0.076743ms) +✔ authorize: not an object (0.066971ms) +✔ authorize: no id (0.065534ms) +✔ authorize: oversize content is accepted and flagged (0.341154ms) +✔ authorize: exactly the limit is not oversize (0.078782ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.427402ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.152876ms) +✔ binding: a complete binding validates and is frozen (2.703261ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.404758ms) +✔ binding: empty allowlists refuse (0.468185ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.290011ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.931725ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.586816ms) +✔ binding: file must be 0600, regular, not a symlink (1.532893ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.291292ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (90.132991ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.903695ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (343.905667ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (200.174989ms) +✔ cli: run refuses when STOP is present, before any network use (171.523856ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.922018ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.616284ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (20.768047ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.490975ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.391857ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.038882ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.14398ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.227966ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.317895ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.045825ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.673464ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.694267ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.512094ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.427935ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.74349ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.786994ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.391427ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.308482ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.08345ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.414452ms) +✔ 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.374678ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.843676ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.576255ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.252481ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.857941ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.417438ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.739374ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.981426ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.829847ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.451565ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.197922ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.134524ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.709508ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.407249ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (60.73066ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (49.279288ms) +✔ engine: one prompt, one turn, text and usage come back (34.617313ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (332.337368ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (233.340952ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.772282ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (235.888971ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (136.711037ms) +✔ engine: when pi has not started a timed-out turn by the end of the abort grace, the engine stops pi and fails held prompts (211.888838ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.022055ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.320587ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.474722ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.384543ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.15694ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (50.872219ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.496157ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.757068ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.531936ms) +✔ gateway: op 9 resumable resumes (0.373098ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.013184ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.569434ms) +✔ gateway: close() is final and unparseable frames are ignored (0.409186ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (64.596811ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (40.976755ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (67.83755ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.314968ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (93.755182ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (96.656828ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (98.248538ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (203.898863ms) +✔ git: push pushes the named branch only and reports up to date (84.702824ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (123.474492ms) +✔ git: the credential helper answers get over https from a private file and nothing else (242.222469ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (771.123817ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.311239ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (80.233294ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.761684ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.369784ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (41.448098ms) +✔ 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 (326.999184ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.47773ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.131511ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.01056ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (48.78091ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (141.323211ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (117.941621ms) +✔ notices: a kind is recorded per UTC day and found again (0.692034ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.770257ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.314538ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.875652ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (44.13958ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (39.725064ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (28.447422ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (81.715611ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (719.569643ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.551388ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.685105ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.967341ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.022929ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.053617ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.837344ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.756482ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.507394ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.922931ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (22.553003ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (9.298935ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.55584ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.329623ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.450254ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (1.960974ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1008.326549ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (1.159338ms) +✔ 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.878604ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.688281ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.227088ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.412164ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (2.77565ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.588564ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (3.425507ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (2.566279ms) +✔ tools: listing and search caps hold (7.60629ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.574731ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (4.162789ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (0.912937ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.964881ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (3.955945ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.44194ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.42934ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.423414ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.386179ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1026.578187ms) +✔ 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.487335ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (1.15617ms) +✔ 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.56656ms) +✔ 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.706348ms) +ℹ tests 178 +ℹ suites 0 +ℹ pass 178 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2647.039322 diff --git a/agents/rocko/work/s4-follow-up/out/node-ledger.txt b/agents/rocko/work/s4-follow-up/out/node-ledger.txt new file mode 100644 index 00000000..408c2464 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-ledger.txt @@ -0,0 +1,86 @@ +✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (165.924047ms) +✔ the raw path accepts nothing else, and refuses before curl runs (406.055497ms) +✔ the file checks still apply on the raw path: mode, symlink, missing, directory (359.792039ms) +✔ the raw path base URL has no override (61.084155ms) +✔ the JSON path is unchanged, and JSON never falls through to the raw path (276.479897ms) +✔ a file that changes between the two reads refuses before curl runs, with or without a body (929.515573ms) +✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (892.631999ms) +✔ real helper GET HTTP 200 preserves exit 0 without credentials (17.119021ms) +✔ real helper POST HTTP 201 preserves exit 0 without credentials (14.717875ms) +✔ real helper GET HTTP 403 preserves exit 1 without credentials (10.164851ms) +✔ fixture git subjects only, follow-ups and three session kinds (136.108668ms) +✔ text and JSON carry same numbers, open and truncated title (199.370787ms) +✔ missing credentials exit 2, no-issues never calls API and shows unknown (154.34927ms) +✔ empty range gives no rows and zero totals (100.922076ms) +✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (135.623258ms) +✔ close-only issue included, even median, missing metadata stays unknown (125.027146ms) +✔ unique commits but per-issue links count multiple tags once each (113.056961ms) +✔ page cap refuses rather than silently undercounting (106.228218ms) +✔ bad API payload not JSON refuses (97.205569ms) +✔ bad API payload {} refuses (100.975217ms) +✔ bad API payload [{"number":1}] refuses (107.560107ms) +✔ partial or malformed session log refuses with location, not content (117.155111ms) +✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (132.521872ms) +✔ no sessions is an empty table; symlink source refuses (228.293901ms) +✔ reads only refactor even when another branch is checked out (145.248816ms) +✔ invalid dates, reverse dates and duplicate options refuse (86.724784ms) +✔ T3 agent assignments do not count as human in Table 2 (131.423095ms) +✔ preamble parsing and issue number boundaries (0.587999ms) +✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.183559ms) +✔ no closed issues with human messages means undefined ratio, not invented zero (0.231127ms) +✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (264.621647ms) +✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (537.303375ms) +✔ T3: a HOME with no database exits 1 and names --no-t3 (136.196572ms) +✔ T3: a file that is not a database exits 1 and names --no-t3 (163.770735ms) +✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (156.237364ms) +✔ T3: an unmapped thread addressed as a seat exits 1 (198.157112ms) +✔ T3: a header to another thread id is not cross-checked (141.948768ms) +✔ T3: no project, or two, for this root exits 1 (338.133774ms) +✔ T3: a removed column exits 1 and names it (126.881768ms) +✔ T3: a missing table exits 1 and names it (120.390088ms) +✔ T3: a counted row with an unknown role exits 1 without its text (147.573714ms) +✔ T3: a counted row with non-text content exits 1 without its text (129.34913ms) +✔ T3: a counted row with an unparseable created_at exits 1 without its text (123.687867ms) +✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (245.917864ms) +✔ T3: a human message with no event counts in humanWithoutEvent (146.529273ms) +✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (229.767827ms) +✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (239.156876ms) +✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (80.39157ms) +✔ T3: a symlink at ~/.t3 exits 1 (96.097141ms) +✔ T3: a symlink at ~/.t3/userdata exits 1 (96.814724ms) +✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (98.58124ms) +✔ T3: with --t3-db, a symlinked file or directory exits 1 (232.307406ms) +✔ T3 WAL: the newest message only in -wal, writer attached, is counted (158.189271ms) +✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (144.472439ms) +✔ T3 WAL: -wal without -shm in a writable directory is read (157.355895ms) +✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (171.762565ms) +✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (149.387801ms) +✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5121.802284ms) +✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.909114ms) +✔ an issue several rows close is expected closed only once all of them are done (0.508786ms) +✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.348898ms) +✔ each owner of an in-progress or in-review row gets one liveness class (8.123876ms) +✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.416449ms) +✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.24282ms) +✔ the text section always ends in a count and a result, and never prints a full pass (0.356876ms) +✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (31.89034ms) +✔ issue states: open list first, then the metric page, then at most 10 lookups (333.535581ms) +✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (434.294609ms) +✔ the open list refuses on a failed call or a bad record, and never echoes the helper (208.448436ms) +✔ a helper call past the deadline is killed with its child, and the call reports it (2012.242172ms) +✔ readQueue loads queue.json through the queue validator and refuses anything else (135.29559ms) +✔ protected changes list every in-range entry that changes a required or parked row (413.044318ms) +✔ the CLI prints the queue section above the weekly table and under a queue key in --json (588.933257ms) +✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (241.511182ms) +✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (308.831682ms) +✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (252.500929ms) +✔ --unsupported-runtime repeats once per seat and takes a seat name (380.580852ms) +✔ an unreadable config makes every owner invalid instead of passing them (134.874241ms) +ℹ tests 78 +ℹ suites 0 +ℹ pass 78 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 12227.76449 diff --git a/agents/rocko/work/s4-follow-up/out/node-mosaic.txt b/agents/rocko/work/s4-follow-up/out/node-mosaic.txt new file mode 100644 index 00000000..6d682e27 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-mosaic.txt @@ -0,0 +1,77 @@ +✔ pure resolution selects current default or explicit enrolled account (2.488438ms) +✔ scope is explicit, bounded and never inferred (1.856965ms) +✔ fork pin is preserved against default change, override, missing account and revocation (0.842962ms) +✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.871202ms) +✔ only explicit synthetic credential forms and internal fixture stores admitted (6.83248ms) +✔ two concurrent workspaces of the same agent publish distinct complete private generations (64.479537ms) +✔ same execution ID is exclusively claimed and cannot overwrite a published generation (41.084097ms) +✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (50.804275ms) +✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (61.293179ms) +✔ credential lock contention refuses without duplicate side effects (12.718151ms) +✔ symlinked pre-existing final target is refused and never followed (35.009186ms) +✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.347623ms) +✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (29.526903ms) +✔ expired credentials refresh under transaction and subsequent generation reuses rotation (110.59713ms) +✔ refresh failure retains prior generation and store state (77.146993ms) +✔ refresh timeout retains prior generation and store state (153.356722ms) +✔ refresh malformed retains prior generation and store state (66.800371ms) +✔ concurrent refresh on same account refuses contention while unrelated account proceeds (216.019093ms) +✔ invalid refresh options refuse before burning claim (33.899759ms) +✔ fixed fake process rotates both OAuth fields without mutating caller input (43.57806ms) +✔ concurrent isolated processes preserve separate provider credentials (35.326457ms) +✔ fake failure is refused with fixed diagnostics (26.933496ms) +✔ fake malformed is refused with fixed diagnostics (34.527734ms) +✔ fake timeout is refused with fixed diagnostics (105.207471ms) +✔ fake unchanged is refused with fixed diagnostics (29.924808ms) +✔ caller executable/environment injection is rejected before spawning (0.404432ms) +✔ valid fixture tree validates and lists without secrets (77.023191ms) +✔ unknown-field refuses (0.415288ms) +✔ invalid-id refuses uppercase and traversal shapes (0.392754ms) +✔ plain-http baseUrl requires allowInsecureTransport (0.400835ms) +✔ native provider rejects allowInsecureTransport (0.173921ms) +✔ unsupported credential type and kind refuse (0.204613ms) +✔ account provider-path mismatch refuses (0.145764ms) +✔ profile account refs must be provider/account shaped (0.276049ms) +✔ seat selection accepts fork pin field, validates account refs (0.263953ms) +✔ harness manifest id must equal executable (gate 1) (0.823761ms) +✔ CLI validate: duplicate provider id across files refuses (32.637734ms) +✔ CLI validate: missing referenced provider/account refuse (44.381341ms) +✔ CLI validate: broken JSON refuses without secret echo (34.151247ms) +✔ CLI usage errors exit 2 (60.973031ms) +✔ credential.json sibling presence does not break validation and is never read (78.431077ms) +✔ D1 missing, empty and structurally empty roots refuse, no list projection (215.152309ms) +✔ D1 required directory auth cannot be absent (66.284061ms) +✔ D1 required directory auth/providers cannot be absent (69.149537ms) +✔ D1 required directory auth/accounts cannot be absent (77.579276ms) +✔ D1 required directory auth/settings cannot be absent (81.001525ms) +✔ D1 required directory harnesses cannot be absent (67.7753ms) +✔ D1 root file and unreadable metadata refuse (116.45675ms) +✔ D2 no symlink traversal at auth/providers/openai-codex.json (59.550832ms) +✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (73.008484ms) +✔ D2 no symlink traversal at auth/providers (70.691711ms) +✔ D2 no symlink traversal at auth (59.78201ms) +✔ D2 root and ancestor symlinks and lexical traversal refuse (190.390235ms) +✔ private filesystem modes enforced for root (60.425069ms) +✔ private filesystem modes enforced for auth (65.249523ms) +✔ private filesystem modes enforced for auth/providers/openai-codex.json (73.927662ms) +✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (76.003834ms) +✔ D3 numeric version 1 only across all record kinds (1.616278ms) +✔ D4 nested unknown keys and missing per-kind required fields refuse (74.668603ms) +✔ D5 unenrolled default refuses even when account exists (75.477908ms) +✔ D6 provider/account credential type must match (71.53665ms) +✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.556494ms) +✔ D8 URLs reject embedded credentials and unsupported protocols without echo (195.246658ms) +✔ D9 malformed JSON diagnostics contain no content excerpt (74.979807ms) +✔ D10 missing metadata is missing-path, not invalid-json (69.179398ms) +✔ D10 library returns no partial entries on any invalid record (75.675783ms) +✔ null/scalar/array metadata refuses without stack or echo (265.801703ms) +✔ credential sibling is never opened, even when an unreadable symlink (32.740301ms) +✔ oversized metadata refuses before parsing (67.809426ms) +ℹ tests 69 +ℹ suites 0 +ℹ pass 69 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2488.4192 diff --git a/agents/rocko/work/s4-follow-up/out/node-queue.txt b/agents/rocko/work/s4-follow-up/out/node-queue.txt new file mode 100644 index 00000000..bde6dc84 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-queue.txt @@ -0,0 +1,158 @@ +✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1267.472445ms) +✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1177.783651ms) +ℹ git commit -e: index.lock free during the editor +✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (1073.585632ms) +ℹ git commit -e -- src.txt: index.lock held during the editor +✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1159.263234ms) +✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1127.159868ms) +✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1167.392219ms) +✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1544.416864ms) +✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1087.312895ms) +✔ F1: a shared-index change during the procedure is not committed (1174.36203ms) +✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1188.320928ms) +✔ F1: a missing or a different hook refuses (779.267672ms) +✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1290.921047ms) +✔ F1: the canary refuses a hook that git would not run (688.256011ms) +✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1165.557182ms) +✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (899.651184ms) +✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (911.176517ms) +✔ bootstrap: the archived tests and validator run outside any repository (889.140742ms) +✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (908.359983ms) +✔ general: a queue write after the snapshot is not committed (1350.190221ms) +✔ general: a snapshot whose log does not extend the base refuses (1006.207946ms) +✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (196.755697ms) +✔ general: environment overrides, a linked worktree and usage (582.084464ms) +✔ general: a queue path staged before the run refuses at step 1 (689.057933ms) +✔ general: HEAD's queue tests failing in the archive refuse (985.695073ms) +✔ genesis document serializes deterministically and replays (4.67164ms) +✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.63623ms) +✔ a tampered result, receipt or viewSha fails replay (2.897793ms) +✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.201786ms) +✔ add: defaults for an ordinary seat, privileged extras, refusals (5.031203ms) +✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (7.046382ms) +✔ matrix: release, review round, changes requested and waiting-on-jason (13.550117ms) +✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (8.710419ms) +✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (19.262517ms) +✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.40439ms) +✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1607.188383ms) +✔ matrix: blocked keeps the claim and returns only to previousState (4.309894ms) +✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.996456ms) +✔ field edits: who may change what (5.549845ms) +✔ set issues keeps a logged narrowing of closes (N10) (2.919138ms) +✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.0547ms) +✔ every accepted text renders to nine cells on every row (N8) (24.715138ms) +✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.585192ms) +✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.821812ms) +✔ replay holds every op id to the caller's rule (N11) (5.613134ms) +✔ note: owner, listed reviewer or privileged; empty clears (1.215475ms) +✔ assign moves the claim with the owner; done clears it (2.73492ms) +✔ render is byte-stable and escapes pipes (0.545087ms) +✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.741249ms) +✔ next: resume, then review, then start, then wait, then nothing; lowest id first (20.61101ms) +✔ canonical args make a retry's identity independent of list order (0.329425ms) +✔ manifests, headings and blob ids (0.488475ms) +✔ the migration map: one queue-map block, exact keys (0.630741ms) +✔ every call but `queue` reaches the seat CLI exactly as before A2 (665.163411ms) +✔ `queue` reaches the queue CLI with the rest of the arguments (216.543764ms) +✔ the pre-A2 fixture is the script A2 changed (0.283762ms) +✔ acquire publishes the record by link; release removes only its own lock (7.848872ms) +✔ a kill between the temp write and the link leaves no lock (47.824153ms) +✔ a short or failed temp write refuses and leaves no lock and no temp (2.247722ms) +✔ a link error other than EEXIST refuses (1.275562ms) +✔ an error after the link releases the lock: unreadable gate, failing temp stat (3.22735ms) +✔ a release that fails on a gate path is reported, never a stack trace (P1) (5.585558ms) +✔ a paused holder: another writer waits 10 s, then refuses naming it live (10068.407105ms) +✔ two concurrent unlockers: the second refuses on the gate (26.175642ms) +✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (1.709441ms) +✔ a writer publishing during an unlock, gate first: the writer releases and refuses (1.681053ms) +✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (2.046227ms) +✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (3.38893ms) +✔ the same pid and start on a different boot is mismatch (0.694945ms) +✔ a foreign host is unknown whatever the local pid says; unlock refuses (50.608594ms) +✔ unreadable /proc: classification is unknown and acquire refuses (0.687618ms) +✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.207121ms) +✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (3.797254ms) +✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (2.367947ms) +✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (1.67287ms) +✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (5.433797ms) +✔ the migration map validates and renders the golden genesis table (3.864429ms) +✔ the marked QUEUE.md holds every row and parked item between its markers (1.678386ms) +✔ map-check reports each kind of drift (5.968189ms) +✔ a request posts once as the requester; a retry sends nothing (608.748996ms) +✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (3553.150687ms) +✔ the pre-send checks: GET user must name the requester, under the deadline (1053.192883ms) +✔ the lead's request refuses a token for login sage (590.274225ms) +✔ the credential file: the seat's own, 0600, no symlink, never the shared default (509.216864ms) +✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1753.612336ms) +✔ a same-op retry after a kill sends nothing, even with a stale view (2671.507409ms) +✔ a held lock at the outcome exits 3 and names what the transport said (616.752838ms) +✔ late outcomes: after an abandon, and after a resolve with the same or another id (1735.746071ms) +✔ resolve checks the comment: issue, markers, round, candidate and author (1684.588505ms) +✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (670.185663ms) +✔ validateRow checks a request round's shape, which every replayed entry must keep (446.136054ms) +✔ request, changes, a new candidate, approval: every round pinned; no review files (1453.335613ms) +✔ a row with no reviewers opens a round that posts nothing (860.567174ms) +✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1222.264465ms) +✔ semantics: v1 entries replay as before; review entries need v2 (369.019506ms) +✔ set reviewers refuses the row's owner (2026-09-28) (302.37542ms) +✔ the owner records no verdict, even as a listed reviewer (372.180331ms) +✔ a request comment over the length limit is not sent (458.430535ms) +✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (423.543333ms) +✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1936.775462ms) +✔ genesis: refusals before anything is written (446.203952ms) +✔ genesis: the map must be committed, well formed, with committed briefs and seats (648.378342ms) +✔ genesis: markers, a stray witness, once only; a retry returns the receipt (625.787436ms) +✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (329.164259ms) +✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (699.498238ms) +✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (605.937281ms) +✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (708.605587ms) +✔ a retried add returns the id it first allocated, after reassignment and after done (664.910717ms) +✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (502.017358ms) +✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (940.318816ms) +✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (690.881078ms) +✔ add, set reviewers and assign refuse the row's owner as a reviewer (455.505712ms) +✔ the working-brief check: a changed working copy refuses the start and flags next (662.413569ms) +✔ next: resume first, then nothing for an idle seat; needs a seat (321.302931ms) +✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (674.737431ms) +✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1200.927341ms) +✔ a hand edit to queue.json refuses every verb, reads included (610.012598ms) +✔ verify and render --check leave bytes and mtimes unchanged (457.244762ms) +✔ render is byte-stable across runs and repositories (279.672708ms) +✔ snapshot and verify --snapshot (830.80842ms) +✔ usage errors exit 4 (618.590269ms) +✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (180.944534ms) +✔ a rename failure: nothing visible, temp removed (133.754187ms) +✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (167.147416ms) +✔ a directory fsync failure, then sync names the op (320.250141ms) +✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (151.888162ms) +✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (133.487295ms) +✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (164.768232ms) +✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (149.736024ms) +✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (189.205094ms) +✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (153.039097ms) +✔ unlock prints a swapped gate's warning on stderr, the result on stdout (117.886054ms) +✔ a view write that fails keeps the op and reports a stale view (121.523798ms) +✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (597.292669ms) +✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (647.495398ms) +✔ SIGKILL after the witness, before the view: the stale refusal names the op (629.675381ms) +✔ SIGKILL after the view, before the receipt: the retry returns the receipt (605.569834ms) +✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (141.574196ms) +✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (923.684788ms) +✔ a deleted witness: refused after the locked recheck; accept-history records it absent (198.053439ms) +✔ a header edit during a write: the op stands, the view write is skipped with a warning (127.634229ms) +✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (125.11438ms) +✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (149.132546ms) +✔ a writer paused before and after the witness rename: readers see a tail, then a match (163.237756ms) +✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (526.95369ms) +✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (488.501315ms) +✔ the platform check refuses other filesystems (129.376413ms) +✔ tmpfs passes only a test layer that allows it (N5) (188.65551ms) +✔ unlock keeps a multi-line lock record on stdout (P3) (190.345833ms) +ℹ tests 148 +ℹ suites 0 +ℹ pass 148 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 24374.869593 diff --git a/agents/rocko/work/s4-follow-up/out/node-seat.txt b/agents/rocko/work/s4-follow-up/out/node-seat.txt new file mode 100644 index 00000000..548eeedc --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-seat.txt @@ -0,0 +1,27 @@ +✔ resolveSeat: by name under --repo resolves the repo layout (1.614406ms) +✔ resolveSeat: by path resolves the fleet layout (0.438607ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.927604ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.834961ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (1.381157ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (2.263772ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (2.387902ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (39.06825ms) +✔ CLI launch: --harness lands in the record (36.375336ms) +✔ CLI launch: the launch script's own exit code passes through (35.303199ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (69.126645ms) +✔ CLI launch: omitting --task records an empty string, not null (39.089153ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (110.908399ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (183.525014ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.530911ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.73824ms) +✔ validateRegistration/readRegistration: taskSetBy is optional; a record without it (written before #1511) still loads unchanged; an invalid one is refused; the version does not change (0.987699ms) +✔ updateTask: records setBy, preserves startedAt and every unrelated field, upgrades an old record in place only when the task is set, and replaces a previous attribution (14.887655ms) +✔ CLI seat task: --by beats MOSAIC_AGENT_NAME, the environment beats nothing, empty environment is unknown, invalid --by or environment refuses with exit 4 and leaves the record byte for byte (329.05029ms) +ℹ tests 19 +ℹ suites 0 +ℹ pass 19 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 947.479624 diff --git a/agents/rocko/work/s4-follow-up/out/node-tasks.txt b/agents/rocko/work/s4-follow-up/out/node-tasks.txt new file mode 100644 index 00000000..1ce65646 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-tasks.txt @@ -0,0 +1,59 @@ +✔ the boot config is checked before anything starts (15.05903ms) +✔ a business with no tracker entry refuses task verbs (13.206278ms) +✔ credential.expiring and .expired are recorded once per instance (31.54058ms) +✔ a token file that changes on disk records credential.changed (12.932978ms) +✔ autostart polls, reconciles and retries a startup the tracker was down for (36.134415ms) +✔ a refusal a restart must clear is not retried by the poll (10.023738ms) +✔ a poll that fires while two are queued is dropped (13.172448ms) +✔ close waits for a running verb and refuses one that has not started (43.815995ms) +✔ the bundled Vikunja is the pinned upstream image the runbook names (0.841445ms) +✔ every published port is on 127.0.0.1, and no secret is in the file (0.323511ms) +✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (99.216958ms) +✔ the recorded task bodies pass the checks S3 applies to every read (0.703623ms) +✔ the client works against the fake over real HTTP with the platform fetch (109.227397ms) +✔ a correct install starts, and the first reconcile records tasks that already exist (27.003598ms) +✔ verbs refuse while a business is starting and after startup refused it (18.409758ms) +✔ startup refuses a token that can do more than its role needs (37.708403ms) +✔ startup refuses an unsupported version and flags an untested one (27.75872ms) +✔ startup refuses a board that the runbook did not install (35.737928ms) +✔ startup refuses a project the sync bot cannot read (7.660232ms) +✔ startup refuses a configured label the pm bot cannot see (8.774459ms) +✔ startup refuses an expired credential and a missing sync credential (17.994987ms) +✔ an unreachable tracker refuses with tracker-unavailable (8.207959ms) +✔ an edit in the UI is recorded once, with the fields that changed (84.516069ms) +✔ a move between open buckets is seen on the board, though updated does not change (40.375056ms) +✔ a person's comment is counted and a bot's is not (71.753693ms) +✔ the hourly reconcile catches a comment through comment_count (79.835253ms) +✔ a task closed in the UI leaves the open view with its done bucket (110.806868ms) +✔ a task that leaves the board is recorded as deleted, moved or out of reach (61.66387ms) +✔ a poll that read before a verb wrote does not overwrite the verb (32.839804ms) +✔ a tracker fault during a tick is reported and the next tick catches up (34.990017ms) +✔ a malformed answer refuses the tick with tracker-shape (11.377332ms) +✔ no token value reaches the database, the log or a refusal (44.700715ms) +✔ the first look at a task counts only comments inside the window (35.105901ms) +✔ task.create needs a recorded human request and a requirement id (46.207399ms) +✔ only labels named in the business file can be written (26.968361ms) +✔ task.schedule sets and clears a due date and relations (33.946077ms) +✔ assign and reassign move the role bots and record task.assigned (56.375338ms) +✔ task.update.assigned is for the assignee and records task.state (57.693568ms) +✔ a wrong expected digest records task.conflict and writes nothing (19.007855ms) +✔ a cross-role verb needs a resolved decision, used once (20.518054ms) +✔ task.close needs a verdict; after it every verb refuses with task-done (20.669219ms) +✔ a lost answer is settled by a re-read and never retried (22.291008ms) +✔ a create whose answer is lost is reported uncertain, and the poll finds the task (34.736066ms) +✔ a task the sync bot cannot read refuses and records nothing (14.308492ms) +✔ verbs and polls for one business run one at a time (84.331414ms) +✔ a due date with milliseconds is written to the second (60.706832ms) +✔ every write landed and the final read failed: the verb succeeds and records what it wrote (36.641238ms) +✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (15.172674ms) +✔ a create whose final read fails succeeds and records task.created (33.280859ms) +✔ an edit between the last write and the final read shows as external on the next poll (36.807608ms) +✔ task.created is recorded when a later label write fails (12.149566ms) +ℹ tests 51 +ℹ suites 0 +ℹ pass 51 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 726.078114 diff --git a/agents/rocko/work/s4-follow-up/out/node-webui.txt b/agents/rocko/work/s4-follow-up/out/node-webui.txt new file mode 100644 index 00000000..984408a2 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-webui.txt @@ -0,0 +1,23 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2231.885573ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2515.444403ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (2135.844177ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1394.840864ms) +✔ conversation view: a newer session with no readable history keeps the marker (921.250471ms) +✔ conversation view: seats without history say so and offer no reply (534.275461ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (2292.765977ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (53489.49899ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (2266.991606ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21865.873619ms) +✔ loopback host and board origin fail closed (7.278431ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (83.201298ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (55.32886ms) +✔ unreachable board reports URL; CLI rejects unsupported options (1031.529212ms) +ℹ tests 14 +ℹ suites 0 +ℹ pass 14 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 53784.421 diff --git a/agents/rocko/work/s4-follow-up/out/run.txt b/agents/rocko/work/s4-follow-up/out/run.txt new file mode 100644 index 00000000..45b2a50a --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/run.txt @@ -0,0 +1,21 @@ +node-business exit 0 +node-bus exit 0 +node-cli exit 0 +node-control-board exit 0 +node-conversation exit 1 +node-discord exit 0 +node-ledger exit 0 +node-mosaic exit 0 +node-queue exit 0 +node-seat exit 0 +node-tasks exit 0 +node-webui exit 0 +suite-auth exit 0 +suite-conductor exit 0 +suite-config exit 0 +suite-discord exit 0 +suite-extension-package exit 0 +suite-foundation exit 0 +suite-queue exit 0 +suite-release exit 0 +suite-task exit 1 diff --git a/agents/rocko/work/s4-follow-up/out/suite-auth.txt b/agents/rocko/work/s4-follow-up/out/suite-auth.txt new file mode 100644 index 00000000..ac228760 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-auth.txt @@ -0,0 +1,17 @@ +OK status with missing harness credential exits 3 and still lists accounts +OK status reports harness credential (read-only) + mosaic accounts +OK api key material never reaches output +OK oauth token material never reaches output +OK unparseable credential file exits 2 +OK symlinked credential file exits 4 +OK env-side credential names reported +OK env var values never reach output +OK accounts without an accounts dir reports none and creates nothing +OK accounts lists files and marks the active one +OK loose account perms flagged in listing +OK agent --auth with missing account file refuses (exit 4) +OK agent --auth with non-0600 account file refuses +OK agent --auth with invalid account name refuses +OK auth.sh without valid config refuses + +selftest: 15 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-conductor.txt b/agents/rocko/work/s4-follow-up/out/suite-conductor.txt new file mode 100644 index 00000000..62afc513 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-conductor.txt @@ -0,0 +1,23 @@ +On branch refactor +Your branch is up to date with 'origin/refactor'. + +nothing to commit, working tree clean +OK dry-run: allowed change, exit 0, nothing committed (exit 0) +OK dry-run committed nothing +OK apply: allowed change exits 0 (exit 0) +OK apply: attribution in commit subject +OK apply: target tree clean after commit +OK disallowed path refused (exit 1) +OK disallowed path: target untouched +OK syntax gate refused broken .mjs (exit 1) +OK syntax gate: target untouched +OK suite failure refused (exit 1) +OK suite failure: target reverted to clean +OK disabled policy refused (exit 2) +OK disabled policy: target untouched +OK failed run refused (exit 1) +OK failed run: target untouched +OK missing run exits 4 (exit 4) +OK invalid policy exits 2 (exit 2) + +selftest: 17 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-config.txt b/agents/rocko/work/s4-follow-up/out/suite-config.txt new file mode 100644 index 00000000..76c05ef7 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-config.txt @@ -0,0 +1,26 @@ +OK absent adapter defaults to pi +OK adapter mock validates (exit 0) +OK unsupported adapter exits 2 (exit 2) +OK env exports adapter +OK bootstrap creates default when absent (exit 0) +OK bootstrap wrote config file +OK bootstrap is idempotent on existing config (exit 0) +OK bootstrap did not rewrite existing config +OK validate missing config exits 3 (exit 3) +OK malformed JSON exits 2 (exit 2) +OK unsupported configVersion exits 2 (exit 2) +OK unknown top-level key exits 2 (exit 2) +OK unknown execution key exits 2 (exit 2) +OK unsupported backend exits 2 (exit 2) +OK unsupported environment exits 2 (exit 2) +OK relative dataRoot exits 2 (exit 2) +OK non-canonical dataRoot exits 2 (exit 2) +OK filesystem root dataRoot exits 2 (exit 2) +OK home directory dataRoot exits 2 (exit 2) +OK dataRoot containing config dir exits 2 (exit 2) +OK control character in provider exits 2 (exit 2) +OK symlinked config file exits 2 (exit 2) +OK env exports resolve correctly +OK failed validation modified nothing + +selftest: 24 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-discord.txt b/agents/rocko/work/s4-follow-up/out/suite-discord.txt new file mode 100644 index 00000000..b013f9ff --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-discord.txt @@ -0,0 +1,70 @@ +toolchain: node v26.8.1 + +OK syntax: packages/discord/src/approvals.mjs +OK syntax: packages/discord/src/authorize.mjs +OK syntax: packages/discord/src/binding.mjs +OK syntax: packages/discord/src/cli.mjs +OK syntax: packages/discord/src/connector.mjs +OK syntax: packages/discord/src/context.mjs +OK syntax: packages/discord/src/engine-pi.mjs +OK syntax: packages/discord/src/errors.mjs +OK syntax: packages/discord/src/gateway.mjs +OK syntax: packages/discord/src/git.mjs +OK syntax: packages/discord/src/journal.mjs +OK syntax: packages/discord/src/notify.mjs +OK syntax: packages/discord/src/rest.mjs +OK syntax: packages/discord/src/setspark.mjs +OK syntax: packages/discord/src/tools.mjs +OK syntax: packages/discord/src/web.mjs +OK syntax: packages/discord/bin/git-credential.mjs +OK syntax: packages/discord/extension/tools.mjs +OK syntax: packages/discord/tests/approvals.test.mjs +OK syntax: packages/discord/tests/authorize.test.mjs +OK syntax: packages/discord/tests/binding.test.mjs +OK syntax: packages/discord/tests/connector.test.mjs +OK syntax: packages/discord/tests/context.test.mjs +OK syntax: packages/discord/tests/engine.test.mjs +OK syntax: packages/discord/tests/fake-pi.mjs +OK syntax: packages/discord/tests/gateway.test.mjs +OK syntax: packages/discord/tests/git.test.mjs +OK syntax: packages/discord/tests/helpers.mjs +OK syntax: packages/discord/tests/journal.test.mjs +OK syntax: packages/discord/tests/notify.test.mjs +OK syntax: packages/discord/tests/recover.test.mjs +OK syntax: packages/discord/tests/rest.test.mjs +OK syntax: packages/discord/tests/setspark.test.mjs +OK syntax: packages/discord/tests/tools.test.mjs +OK syntax: packages/discord/tests/web.test.mjs +OK syntax: packages/discord/fixtures/claim-worker.mjs +OK syntax: packages/discord/fixtures/legacy-owner-worker.mjs +OK syntax: scripts/discord.sh +OK syntax: scripts/discord-service.sh +OK packages/discord declares no dependencies +OK no bot-token-shaped string in packages/discord +OK fixture binding uses placeholder ids only +OK fixture binding validates +OK real pi with the extension exposes exactly list_dir, read_file, search and no built-in tool +OK real pi with a writable root exposes exactly the three reads plus write_file and edit_file, and writes nothing at start +OK real pi with a web key exposes the three reads plus web_fetch and web_search, and no write tool without a writable root +OK real pi with a git root exposes the reads, writes and the four git verbs, commits nothing at start, and never shows the token +OK real pi with protocol vault adds reserve_id to the git verbs +OK real pi with a setspark key exposes the reads and the eight record verbs, no counters, and never shows the key +OK real pi refuses a git key on a read-only root (fail closed) +OK real pi with the pilot flags (--no-tools) exposes no tool at all +OK real pi exits non-zero without MOSAIC_DISCORD_TOOLS: no session, no tools (fail closed) +OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT +OK node --test packages/discord/tests/ (ℹ pass 178) +OK scripts/discord.sh --help exits 0 +OK scripts/discord.sh check without a binding exits 4 +OK scripts/discord.sh recover without a binding exits 4 +OK scripts/discord.sh reload without a binding exits 4 +OK scripts/discord-service.sh without a command exits 4 +OK service unit renders with the repository path, a supervised run as the main process, exit 3 never retried, and reload as SIGHUP +OK service install writes the rendered unit (0644) and leaves no temp file +OK service install a second time reports unchanged +OK systemd-analyze verify accepts the rendered unit +OK service uninstall removes the unit file +OK service install with an unknown flag exits 4 +OK service install with USER unset finishes and names the account for lingering + +discord suite: 66 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-extension-package.txt b/agents/rocko/work/s4-follow-up/out/suite-extension-package.txt new file mode 100644 index 00000000..05a7d292 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-extension-package.txt @@ -0,0 +1,21 @@ +OK initial ordinary-file install +OK installed tree matches canonical source +OK installed tree has no symlinks +OK check detects installation drift +OK sync refuses to overwrite installation drift +OK check detects an extra destination file +OK check detects an extra destination directory +OK check rejects a destination symlink +OK sync accepts a canonical source update +OK updated installation matches canonical source +scripts/test-extension-package.sh: line 14: 3722098 Killed "$@" > /dev/null 2>&1 +OK forced interruption kills the replacing process +OK next invocation recovers old consistent installation +OK interrupted replacement rolled back +OK sync succeeds after interruption recovery +OK unlocked stale lock file does not block +OK active lock refuses a concurrent sync +OK source symlink fails closed +OK nested second entrypoint fails closed + +extension package selftest: 18 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-foundation.txt b/agents/rocko/work/s4-follow-up/out/suite-foundation.txt new file mode 100644 index 00000000..6338e1e6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-foundation.txt @@ -0,0 +1,53 @@ +toolchain: node v26.8.1, python 3.12.8, jsonschema 4.26.0 + +OK syntax: scripts/foundation-inspect.mjs +OK syntax: scripts/foundation/strict-json.mjs +OK syntax: scripts/foundation/canonical.mjs +OK syntax: scripts/foundation/resolve.mjs +OK syntax: scripts/foundation/validate-record.mjs +OK syntax: scripts/foundation/fixtures/build-fixtures.mjs +OK syntax: scripts/foundation/canonical.test.mjs +OK syntax: scripts/foundation/cli.test.mjs +OK syntax: scripts/foundation/fixtures.test.mjs +OK syntax: scripts/foundation/resolve.test.mjs +OK syntax: scripts/foundation/strict-json.test.mjs +OK syntax: scripts/foundation/verify-schema.py (ast only; no bytecode written) +OK fixture generator runs +OK checked-in fixtures/bundles equal a fresh generation +OK checked-in fixtures/raw equal a fresh generation +OK checked-in fixtures/index.json equal a fresh generation +OK checked-in demo bundles equal a fresh generation +OK a failing nested test fails the run under a parent runner's NODE_TEST_CONTEXT +OK node --test scripts/foundation/ (ℹ pass 80) +OK differential schema oracle: PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof) + platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999) + node v26.8.1; corpus 1568 records (38 pinned fixtures, 478 unique bundle records, 1052 typeCase/mutation/lexical cases) + schema column: agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10 + profile column (schema-valid records only): profile-valid 510, profile-invalid 30 + profile refusals asserted: 30 schema-agreed-valid records refused only by the strict typed-string profile (rule profile-pattern-mismatch), 12 declared by name; 73 named probes verified against declared schema/profile columns +OK oracle: zero schema-column disagreements with the pinned checker +OK oracle: strict-only profile refusals are counted and asserted +OK demo: permitted read preview exits 0 (exit 0) +OK demo: permitted file.change preview exits 0 (exit 0) +OK demo: assignment.change proposal is unresolved (exit 3) (exit 3) +OK demo: revoked registration is refused (exit 3) (exit 3) +OK demo: message is not authority (exit 3) (exit 3) +OK usage: no arguments exits 2 (exit 2) +OK io: missing file exits 4 (exit 4) +OK io: directory exits 4 (exit 4) +OK io: symlink exits 4 (O_NOFOLLOW) (exit 4) +OK bound: oversize fixture exits 2 (exit 2) +OK profile: one final LF in a typed selection id is refused before admission (exit 2) (exit 2) +OK profile: two final LFs fail the schema pattern itself (exit 2) (exit 2) +OK profile: escaped newlines in free-form text stay allowed (exit 0) (exit 0) +OK profile refusal is invalid-request/profile-pattern-mismatch with selection and operation withheld, value not echoed +OK text output starts with the disclaimer +OK json output is valid JSON with result allowed and exactly the charter §7 fields +OK json golden matches byte-for-byte +OK sandboxed bundle run (env -i, PATH=/nonexistent) produced the unresolved proposal +OK sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs +OK canary never printed (bundle run and credential-file run) +OK a non-bundle JSON file is refused at the shape gate, not read into output +OK no field of the non-bundle file is echoed + +selftest: 44 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-queue.txt b/agents/rocko/work/s4-follow-up/out/suite-queue.txt new file mode 100644 index 00000000..cdc93566 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-queue.txt @@ -0,0 +1,35 @@ +toolchain: node v26.8.1, git version 2.55.0 + +OK syntax: packages/queue/src/cli.mjs +OK syntax: packages/queue/src/errors.mjs +OK syntax: packages/queue/src/io.mjs +OK syntax: packages/queue/src/lock.mjs +OK syntax: packages/queue/src/queue.mjs +OK syntax: packages/queue/src/review.mjs +OK syntax: packages/queue/src/store.mjs +OK syntax: packages/queue/tests/commit.test.mjs +OK syntax: packages/queue/tests/data.test.mjs +OK syntax: packages/queue/tests/dispatch.test.mjs +OK syntax: packages/queue/tests/helpers.mjs +OK syntax: packages/queue/tests/lock.test.mjs +OK syntax: packages/queue/tests/migration.test.mjs +OK syntax: packages/queue/tests/review.test.mjs +OK syntax: packages/queue/tests/store.test.mjs +OK syntax: packages/queue/tests/write.test.mjs +OK syntax: packages/queue/tests/fixtures/fake-gitea.mjs +OK syntax: packages/queue/tests/fixtures/kill-at.mjs +OK syntax: packages/queue/tests/fixtures/lock-child.mjs +OK syntax: packages/queue/tests/fixtures/mosaic-pre-a2.sh +OK syntax: scripts/queue-commit.sh +OK syntax: scripts/git-hooks/pre-commit +OK syntax: scripts/mosaic +OK queue-commit.sh, the guard and scripts/mosaic are executable +OK packages/queue declares no dependencies +ℹ tests 148 +ℹ pass 148 +ℹ fail 0 +OK node --test packages/queue/tests/ +OK scripts/mosaic queue help +skip queue verify and render --check: this checkout (/mnt/storage/scratch/rocko-r45/tree) is not the queue's canonical root (/mnt/storage/src/mosaic-stack) + +queue suite: 27 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-release.txt b/agents/rocko/work/s4-follow-up/out/suite-release.txt new file mode 100644 index 00000000..1d6ed050 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-release.txt @@ -0,0 +1,7 @@ +OK valid RELEASE resolves (exit 0) +OK invalid RELEASE exits 1 (exit 1) +OK missing RELEASE exits 1 (exit 1) +OK valid RELEASE leaves image tag consistent with version +skip state-machine cases (docker daemon unavailable) + +selftest: 4 passed, 0 failed diff --git a/agents/rocko/work/s4-follow-up/out/suite-task.txt b/agents/rocko/work/s4-follow-up/out/suite-task.txt new file mode 100644 index 00000000..85aa46f0 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-task.txt @@ -0,0 +1,33 @@ +OK valid task validates (exit 0) +OK unknown task key exits 2 (exit 2) +OK unsupported taskVersion exits 2 (exit 2) +OK invalid task id exits 2 (exit 2) +OK empty prompt exits 2 (exit 2) +OK NUL in expectExact exits 2 (exit 2) +OK out-of-range timeout exits 2 (exit 2) +OK missing mission file exits 4 (exit 4) +OK task with valid mission validates (exit 0) +OK invalid mission exits 2 (exit 2) +OK validate missing task exits 4 (exit 4) +OK validation does not modify the task file +OK prune dry-run exits 0 (exit 0) +OK dry-run deleted nothing +OK prune --keep=2 --yes removes oldest (exit 0) +OK kept exactly 2 newest runs +OK newest run kept, oldest pruned +OK append-only receipt written (3 entries) +OK sessions/workspaces untouched by prune +OK prune with invalid keep exits 4 (exit 4) +skip adapter seam cases (docker daemon unavailable) +skip workspace/capability cases (docker daemon unavailable) +skip live task cases (docker unavailable) +OK onboard without name exits 4 (non-interactive) (exit 4) +OK onboard --name renders profile (exit 0) +OK profile written +OK canon structure: required filled, optional placeholdered +OK canon sections present +FAIL user recall run succeeds (exit 1) +FAIL recalled user name (response: ) +OK no agent identity on headless run + +selftest: 26 passed, 2 failed diff --git a/agents/rocko/work/s4-follow-up/packet-manifest.sha256 b/agents/rocko/work/s4-follow-up/packet-manifest.sha256 new file mode 100644 index 00000000..5c793648 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/packet-manifest.sha256 @@ -0,0 +1,56 @@ +fd7c9a28364d61669889180bce1482449ab0a583228ebb9e0e8fe5075cbe5eb0 BUILD.md +c88b20e4bd185682c82a4ce1733cd5b5a23a5375a10e9de55f2af5f8af7474a9 base.txt +4ed9ff61b94b4c9d2426e5703c2e6c0dbcd6bc0c64a88d59347a8b47f4933408 build.patch +b329fdcbdf8cb62659f239359570dfa9771559e3112b743df55429244769ed14 candidate-manifest.sha256 +8d35f99580981064d08725cf8e3dd67885bff77603446b0022701d4357727273 files.txt +bbe45c2a578b35cc2559972a4ee8900bd01ffb69e72bef6472dfc7e147e8916f mutants.sh +1102fd4f14df908f0498d31d209023d7903ba090c6c3bf3e3338e3f52158d95d out/base-node-conversation.txt +8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/base-suite-task.txt +2fc28669728c05f1f3f2c61e3c3a8fd88e7d13233856739428e772992bfc280b out/load-end-r2.txt +729a1baad006dde35b0b214386114ae6a0406ce302891a6e6aab32af52d025a5 out/load-end.txt +a0f662486a414610599959e6ea14dc9a80ec8ac266b0d4a98f5f26ec16b95396 out/load-start-r2.txt +a78af172868215584d99e036989cb88475fd5eecf8b2ac1775fd68aeb61af76e out/load-start.txt +e1bd7a5920b70115d2251ae3289799bb446445703b9da51c55c668f9cedfa461 out/manifest-check-mut.txt +e1bd7a5920b70115d2251ae3289799bb446445703b9da51c55c668f9cedfa461 out/manifest-check-tree.txt +95bdcddfb56d633c72c0b8c18948cffd42e6c1b2aaa3b78800783eb2d1452e35 out/mut-E1.txt +1e13a94767e411e0eccf09d8b362dba095e33b6403f3d9bc5cde3e7cdfe62315 out/mut-E2.txt +792f305fd87032806ebeba719008f927a8dd11358be89cd73e3e89abe721d5f0 out/mut-F2a.txt +fff65df80a7e894292c7784d86cd1430e46d176e3f0435b6cfa9d9bb2422f9c4 out/mut-F2b.txt +0aa3076b1eace7751bd5b0737c7ae7fd396c6c7a91a4641326a1e43be252ba57 out/mut-F2c.txt +f3822b75aef0317fefd384f5260c0a4f0301240d72ea9f1cff9c0472acec81aa out/mut-F2d.txt +d107089b12645ec903983207985135a5a71ac472eef3465104c3615b78b18e4a out/mut-F2e.txt +d28c92f2defb187c1834b4ba63fb337cc656c1aaf7bf23b83cd80f235be836a3 out/mut-F2f.txt +3aa5bdcbe2cba59ef458b7ad7b524d7b995168c81462a52c638cd3eabb9c5e7e out/mut-G144.txt +05de6f2ae139f346e8f0bc4c1d56bb59bfe83228551f7833c6e9343c8322b303 out/mut-G150.txt +e75b166d3aa8f605d394ed5449c20a11ed884c910c756230697d4f387e13cd50 out/mut-J4a.txt +593f97f881967eeda72a8dab6027a9b58282ce73c490a690114953247af18c2c out/mut-J4b.txt +00aba46c8254eb28a4b5f33d0e5fe73b43fc2c55f9e1b1411096987656f8d45d out/mut-J4c.txt +7bd781744b5a42cf5a21aba340c8e40ae39871ef50c7de7a3913f0532ee9f985 out/mut-J4d.txt +8f72f99fd972c1d9a6a2e8ca391d88317299c56511de8e8dd0402986421fbb03 out/mut-M28.txt +2316655ada74ac0d000ffbb3d27d234a838d036d8ad4266ec147373c841ee9d3 out/mut-N2.txt +a6dc80efac48f9de98748433fcac165b4cc3bf000de2b4c04d9107c9b3dfd342 out/mut-N4.txt +a0df618c85c05350971ed56203a4d8f37e8a6cc0fa2ba267de0ba34fd197523f out/mut-N5.txt +1225000966c8c8c2231ff7091665950f17a6d9d756a46f48b4ae3a8400aa635d out/mutants.txt +f14fe1ee4239ae63dfb1bea143d1d450905387a34abbcb9e4c6b09104b75aefa out/node-bus.txt +1a88ea66c4b4e6cbf23a420628b53082a770828fb10ddf3ae49cd5b65f4855d0 out/node-business.txt +099d2019d935d5c49cecff107316b040a93959ea832375cc008cb8c991004174 out/node-cli.txt +9d2ef4cf44c714e9d18a2162b1c34aad8867ca631d66253cf08c11c96dd6f472 out/node-control-board.txt +bb4541854d7f2ced39d032f84f36088be1961ec7cf7c591c1196c94157c61858 out/node-conversation.txt +9643c28e1490621836a1c64e2b62fad8f44033965cfc68df49187ff5e7c62c33 out/node-discord.txt +c5fdadd964fdccc88182f91fa2b724ec7b69d1246b972c25536cc398800127dd out/node-ledger.txt +fc0d61d1a3695c44bc5e698523bd790e68bfde3785f38526c601bee852ad3784 out/node-mosaic.txt +e2e08d74f8cb590e75d50bc011ca5875d8d49c1470e7f9ee9173cba39946eec9 out/node-queue.txt +74dbaa3099f9e3e88219cdc2ae4b83f21fc18ddafe060b299c1dcc5eacf95528 out/node-seat.txt +34e473be5528b430fb28d9e78b59092e4bedf8660306590b24b6083ca844a697 out/node-tasks.txt +a911209250eef9dbdbb7c762655b29811b42198cf439c363c601993ac30e1a3c out/node-webui.txt +8eaa5212718b37938ab792b00210ad00c7b793259ae9e0befc6f7b0316c1881e out/run.txt +f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/suite-auth.txt +e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/suite-conductor.txt +52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/suite-config.txt +7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/suite-discord.txt +e4c762a4d4225b2f3eab0e37cba1d36126e67465557501196b91c2ffdd576e0e out/suite-extension-package.txt +83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/suite-foundation.txt +bf5db498b82546af2d78a218a1d6446da993de629f4819fffeecf690ef93a314 out/suite-queue.txt +6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/suite-release.txt +8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/suite-task.txt +50ceb870e3f56cac0ea04c7f7995a5eb5ec4e66f9c09a92a9fc7705e1df896cf run.sh diff --git a/agents/rocko/work/s4-follow-up/run.sh b/agents/rocko/work/s4-follow-up/run.sh new file mode 100755 index 00000000..acf428e8 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/run.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Sequential gate in the candidate worktree; each output kept. +# Usage: run.sh [suffix] +T="$1"; O="$2"; X="${3:-}"; cd "$T" +for d in packages/*/tests; do + p=$(basename "$(dirname "$d")") + ls "$d"/*.test.mjs >/dev/null 2>&1 || continue + env -u NODE_TEST_CONTEXT timeout 1200 node --test "packages/$p/tests/*.test.mjs" > "$O/node-$p$X.txt" 2>&1; echo "node-$p exit $?" +done +for s in scripts/test-*.sh; do + n=$(basename "$s" .sh); n=${n#test-} + if [ "$n" = task ] || [ "$n" = release ]; then + env -u NODE_TEST_CONTEXT DOCKER_HOST=unix:///nonexistent-rocko-r45-docker.sock timeout 900 bash "$s" > "$O/suite-$n$X.txt" 2>&1; echo "suite-$n exit $?" + else + env -u NODE_TEST_CONTEXT timeout 900 bash "$s" > "$O/suite-$n$X.txt" 2>&1; echo "suite-$n exit $?" + fi +done