diff --git a/agents/rocko/work/s4-follow-up/BUILD.md b/agents/rocko/work/s4-follow-up/BUILD.md index 60db4577..c03fcdf3 100644 --- a/agents/rocko/work/s4-follow-up/BUILD.md +++ b/agents/rocko/work/s4-follow-up/BUILD.md @@ -1,35 +1,222 @@ -# Row 45 (#1527): S4 follow-up, candidate packet +# Row 45 (#1527): S4 follow-up, candidate packet, round 2 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 +Rulings: lead decision 72, and lead decision 73 with comment 26879 for +round 2. Base: `d539d8d2` (`base.txt`). Round 1 was over `521597bb`. None +of the 8 files changed upstream between the two bases. This packet is uncommitted. There are no commits, pushes or Gitea calls, and no token or private binding was read. +Round 2 answers Darkwing's changes verdict (comment 26872, rev 208) and +Filbert's (comment 26880, rev 211). The round 1 text follows the round 2 +section and still holds, except where round 2 says otherwise. + ## 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 | +| `packages/cli/src/notifier.mjs` | F2 refusal limit, J4 type check, error messages; round 2: the 30-minute refusal wait from the journal, a real-date `day` check, three more error paths | +| `packages/cli/src/host.mjs` | `broker.connected` guards on both start-failure `close` sends; round 2: a callback on all four sends (:144, :150, :184, :188) | +| `packages/cli/README.md` | digest mark, definite-refusal bullet, type-check list, refusal wording; round 2: the refusal wait, manual recovery, the digest-mark sentence, the new error paths | | `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/notifier.test.mjs` | F2, J4, N2, N4 and error-path tests; round 2: spacing, restart, X9, day validity, note 1 paths | +| `packages/cli/tests/host.test.mjs` | reader-cap exposure, M28, N5, both broker-death guards, `t.after`; round 2: three EPIPE tests, the X14 assertion | | `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`). +`build.patch`: `git diff --cached --binary d539d8d2` over those files, ++635/−56. It applies cleanly to d539d8d2 (`out/manifest-check-tree-r2.txt`). -## What changed +## Round 2 + +### R1: a callback on every send that can race a death (both reviews) + +`host.mjs` :144, :150 and :188 now read +`if (broker.connected) broker.send({ op: "close" }, () => {});`, and :184 +reads `if (notify.connected) notify.send({ op: "stop" }, () => {});`. + +`connected` stays true after a child dies until Node processes the +disconnect. A send in that window fails with EPIPE. Without a callback, +Node emits that as an `error` event on the next tick. The `once(child, +"exit")` in `ended()` rejects on `error`, so the raw EPIPE replaces the +real outcome. With a callback, the error goes to the callback and nothing +else sees it. The `exit` still settles `ended()`. + +What the raw EPIPE cost (Filbert's point): + +- **:150**, the refusal path. A notifier refusal is a CliError with exit + 3, which `RestartPreventExitStatus=2 3 4` keeps down. A raw EPIPE is not + a CliError, so `cli.mjs` exits 1 with a stack trace, and the unit + restarts the host every 15 s against a refusal that won't go away. +- **:144**, the no-reply path. The exit code is 1 either way, but the + operator gets the EPIPE instead of the notifier's message. +- **:184 and :188**, in `close()`. A raw EPIPE rejects `close()`, so the + host never removes its state file or finishes `done`. + +Tests (Darkwing's deterministic form). `failSends(t, fake)` in +`host.test.mjs` traps the `send` setter of each new child, as the +reader-cap spy does. When `fake(child, m)` returns an error, the send +fails the way Node fails a write to a dead child: the error goes to the +callback if there is one, or else to an `error` event on the next tick. +`epipe(child)` SIGKILLs the child and returns an EPIPE error. The real +window is about a millisecond wide, so a test that waits for it would be +flaky. This one is not. + +- A close send that fails with EPIPE after the notifier refuses: the host + rejects with exit 3 and `notifier refused to start: … no dmRecipient`. +- A close send that fails with EPIPE after the notifier dies without a + reply: the host rejects with exit 1 and `notifier exited (null) before it + replied`. +- `close()` whose stop and close sends both fail with EPIPE resolves with + 1 and removes the state file. + +All three passed 15 of 15 runs. Without a callback, each one fails (G144cb, +G150cb, G184 and G188 in the mutant table). + +A correction from my first round 2 mutant run: G184 hung the host test file +for its 900 s timeout instead of failing. The `close()` test had +`t.after(() => host.close(0))`. When the mutant made `close()` reject, +`closing` was already set, so the cleanup's second `close()` waited on a +`done` that never settles. The broker it never closed also kept the file +alive. The fix: `failSends` now SIGKILLs every child it saw after each +test, and that test has no `t.after` close. With the fix, G184 and G188 each +fail that test in about 20 s. I stopped the first run, restored the mutated +`host.mjs` from its `.orig` copy (the manifest checked OK), then rebuilt the +patch and re-ran the whole gate and every mutant from scratch. + +### R2 / B1: a definite refusal waits the full 30 minutes (decision 73) + +In round 1, a refusal backed off the same way as an unknown outcome: 30 s, +doubling. So five refusals took 7.5 minutes, and a binding typo gave up +every blocking DM before anyone could fix it. Now: + +- After a definite refusal, the next attempt waits `BACKOFF_MAX_MS` + (30 min). The log line reads `refused (HTTP 403); retry in 1800 s`. +- The wait is counted from the refusal's `at` in the journal. `openJournal` + keeps `refusedAt`, the time of each decision's last definite refusal, and + updates it on every append. `tick` skips a decision until 30 minutes after + that time. A restart rebuilds the map from the file, so it can't shorten + the wait. +- Unknown outcomes keep the doubling backoff from 30 s, with no limit. + +The five sends therefore go at 0, 30, 60, 90 and 120 minutes, and the DM +gives up at 2 hours. + +Tests: + +- **Spacing.** Polled every `POLL_MS` on a fake clock against a permanent + 403: sends at 0, 30, 60, 90 and 120 min, no gave-up line before 120 min, + one at 120 min, and four `retry in 1800 s` log lines. +- **Restart.** Refusals at 0 and 30 min, then a restart at 31 min. The next + send comes at 60 min, not at once. +- The existing backoff test now checks both waits. An unknown outcome logs + `retry in 30 s`. A 403 sends nothing at +29 min, sends at +30 min, and + logs `retry in 1800 s`. + +### README + +- **Recovery after a give-up is manual.** Fixing the binding does not + resend the DM, which stays given up. The decision stays open in `mosaic + inbox`, the digest lists it, and the operator decides it with `mosaic + decide`. +- The definite-refusal bullet gives the 30-minute wait from the journal and + the 2-hour span. The `unknown` bullet keeps the doubling backoff. +- Darkwing's note 4: the digest-mark sentence now quotes its three states, + and the long line is rewrapped. +- The `day` field and the refusal list name the round 2 checks. + +### Optional items taken + +- **X9 (Filbert):** a 0400 `sent.jsonl` in a good directory refuses with + `notify journal is not writable (EACCES): `. +- **X14 (Filbert) and Darkwing's note 2:** the bus-service install test + asserts the `mkdir -m 0700 -p /notify/` line. +- **Darkwing's note 3:** a `day` must be a real date. `2026-13-45` and + `2026-02-30` now refuse as `(day)`. The check is the `YYYY-MM-DD` shape, + then `Date.parse`, then a `toISOString()` round trip. +- **Darkwing's note 1:** the three raw-error paths now refuse with a + CliError and exit 3: + - A dangling symlink in place of the directory: `mkdir` fails with + ENOENT, and an `lstat` finds a link, so the message is `notify journal + directory must not be a symlink`. + - A parent that is a file: ENOTDIR gives `notify journal directory + cannot be created (ENOTDIR)`. + - A `sent.jsonl` that is a directory: EISDIR gives `notify journal must + be a regular file, mode 0600, owned by this user`. + +### Declined: a type check inside `append` (Filbert's note 1) + +Filbert left this to me, and I left it out. A failing check in `append` +would throw inside `attempt`'s catch, which journals and logs the send as +`unknown`. The DM was in fact sent. The next tick would send it again, and +then again on every later tick, so a bad `messageId` would become a loop of +duplicate DMs. Today the writer can't produce a bad line: `rest.mjs` +`createMessage` throws `unknown` on a 2xx without a string id. If that +changes, the open-time check refuses the bad line with exit 3. That is a +loud failure, and it fits fail-closed better than a send loop. + +Darkwing's note 5 (`accessSync` is advisory) needs no change. `open` still +refuses, and only the message could differ. + +### Round 2 gate + +The gate tree `/mnt/storage/scratch/rocko-r45/tree` is now a detached +worktree at d539d8d2 with `build.patch` applied. The manifest checks 8/8 OK +(`out/manifest-check-tree-r2.txt`). `run.sh` ran every node suite and then +every `test-*.sh`, one at a time, with Docker withheld from test-task and +test-release. The outputs are `out/node-*-r2.txt` and `out/suite-*-r2.txt`. +It ran 14:02:34Z to 14:05:56Z, and load average was 1.01 at the start and +2.44 at the end. + +| Suite | Pass / fail | +|---|---| +| node business / bus / cli / control-board | 60/0 · 67/0 · 66/0 · 124/0 | +| node conversation / discord / ledger / mosaic | 152/0 · 178/0 · 78/0 · 69/0 | +| node queue / seat / tasks / webui | 148/0 · 19/0 · 51/0 · 14/0 | +| 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 (Docker cases skipped) | +| test-task | 26/2: "user recall run succeeds", "recalled user name" | + +Conversation passes in full now that row 46 has landed (2d308abd). The two +test-task failures are the live-recall cases that need Docker. They failed +the same way on the round 1 base. I did not rerun the base on d539d8d2, +because none of the 8 files changed between the two bases. + +### Round 2 mutants + +`mutants.sh` re-ran all 18 round 1 mutants and 13 new ones in the gate +tree after the gate (`out/r2/mutants.txt`, `out/r2/mut-*.txt`). It ran +14:05:56Z to 14:08:50Z, and load average was 2.44 at the start and 3.85 at +the end. All **31 of 31** were killed, each by a failing test, with 0 +cancelled. The round 1 mutants were killed by the same tests as in round 1. +F2a and F2f are now also killed by the spacing test. Afterwards the tree +checked 8/8 OK (`out/r2/manifest-check-mut.txt`). + +| Id | Mutation | Killed by | +|---|---|---| +| G144cb | no callback on the :144 close send | EPIPE after the notifier dies unanswered | +| G150cb | no callback on the :150 close send | EPIPE after the notifier refuses | +| G184 | no callback on the `close()` stop send | `close()` with EPIPE on stop and close | +| G188 | no callback on the `close()` close send | `close()` with EPIPE on stop and close | +| R2a | a definite refusal backs off like an unknown | backoff test (the 1800 s log); spacing test | +| R2b | `tick` ignores `refusedAt` | restart after the second refusal | +| R2c | `refusedAt` never set | restart after the second refusal | +| X9 | no CliError for an unwritable `sent.jsonl` | the unwritable-journal test | +| X14 | install message drops the `mkdir -m 0700` line | bus-service.sh install test | +| D3 | `day` is a shape check only | wrong-type table (`2026-13-45`, `2026-02-30`) | +| N1a | no symlink message for a dangling directory link | dangling link, file parent, directory journal | +| N1b | ENOTDIR not turned into a CliError | the same test | +| N1c | EISDIR not turned into a CliError | the same test | + +## Round 1 (still holds unless round 2 says otherwise) ### 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. + count. Unknown outcomes keep retrying 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. @@ -55,22 +242,22 @@ or the first field that fails: - `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. +- `day`: required and a real `YYYY-MM-DD` date for a digest (round 2); + optional for a dm, but valid 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. +Both reviewers agreed with the contested choices: every line is checked, a +digest with a decision is refused, gave-up is dm-only, and `confirmed` +requires a string messageId. ### Error messages (the brief's error items) -- mkdir fails with EACCES, EPERM or EROFS: +- mkdir fails with EACCES, EPERM or EROFS (round 2: or ENOTDIR): `notify journal directory cannot be created (CODE): `. -- Directory is a symlink (checked with lstat): +- Directory is a symlink (checked with lstat; round 2: a dangling one too): `notify journal directory must not be a symlink: `. The 0700 check follows it as before. - Directory not writable (`accessSync` W_OK|X_OK): @@ -85,19 +272,11 @@ 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 +paths are `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. +(ERR_IPC_CHANNEL_CLOSED). Round 1 recorded the remaining window after a +SIGKILL and left it open. Round 2 closes it (R1 above). ### Tests @@ -115,93 +294,33 @@ failure was observed. It is a candidate follow-up if a reviewer wants it. 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. + `error` event and there is no `close` send. - **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. + `error` event and no `close` send. - **`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. + not `t.after`, because a `t.after` restore runs after `tmp()` cleanup. + 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. + can be a live pid. All five uses in that file are replaced. - **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. + `packages/cli/tests/trackers-boot.test.mjs`. Sage's tracked original in + `agents/sage/work/s4-follow-up/` is left for Sage to remove at landing. -## Gate +### Round 1 gate and mutants -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 | +Round 1, over 521597bb, was green except for conversation K1, K3 and K10 +(row 46) and test-task's two live-recall failures, which need Docker and +fail the same way on the base. All 18 round 1 mutants were killed. The +files are `out/node-*.txt`, `out/suite-*.txt`, `out/base-*.txt` and +`out/mut-*.txt` without the `-r2` suffix. Both reviewers reproduced them. ## Hashes -- `build.patch`: `4ed9ff61b94b4c9d2426e5703c2e6c0dbcd6bc0c64a88d59347a8b47f4933408` -- `candidate-manifest.sha256`: `b329fdcbdf8cb62659f239359570dfa9771559e3112b743df55429244769ed14` +- `build.patch`: `c8cec070da60d8297f1ee5b006a1099ab9376fd3abf32fa4461f967750ad6756` +- `candidate-manifest.sha256`: `5b067a9dad645c31d99e1ea02575cd2fc1ba547ce011ea81c56b17ae29da0d0e` - `packet-manifest.sha256`: a sorted sha256 list of every packet file except itself. +- Round 1, for reference: `build.patch` `4ed9ff61…3408`, `candidate-manifest.sha256` `b329fdcb…ed14`. diff --git a/agents/rocko/work/s4-follow-up/base.txt b/agents/rocko/work/s4-follow-up/base.txt index 8f273517..dba20e6c 100644 --- a/agents/rocko/work/s4-follow-up/base.txt +++ b/agents/rocko/work/s4-follow-up/base.txt @@ -1 +1 @@ -521597bbe0cf51a0a1aa3b7a16f13fc7b59c98c6 +d539d8d2cc930ff8707f8d778a78b1fe661a3d99 diff --git a/agents/rocko/work/s4-follow-up/build.patch b/agents/rocko/work/s4-follow-up/build.patch index 53c7ac34..580d2dbe 100644 --- a/agents/rocko/work/s4-follow-up/build.patch +++ b/agents/rocko/work/s4-follow-up/build.patch @@ -1,28 +1,44 @@ diff --git a/packages/cli/README.md b/packages/cli/README.md -index 589078c3..67dc6618 100644 +index 589078c3..0eb1423c 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md -@@ -145,8 +145,8 @@ inbox through the reader capability and does two things: +@@ -145,9 +145,10 @@ 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. +- Discord's 2000 characters. ++ The digest lists the inbox. It marks each blocking decision with one of ++ three states: "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: + `/notify//sent.jsonl` (directory 0700, file 0600), with +@@ -160,21 +161,41 @@ one line per send attempt: + + - A decision, or a day's digest, counts as sent once it has a `confirmed` + line. +-- A `refused` or `unknown` send is retried. The wait starts at 30 s and +- doubles up to 30 min. A duplicate costs less than a miss. Every retry ++- A `refused` or `unknown` send is retried. After an `unknown` outcome the ++ wait starts at 30 s and doubles up to 30 min. A duplicate costs less than ++ a miss. Every retry 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). ++- A definite refusal is a DM refused with an HTTP 4xx other than 429. The ++ next attempt waits the full 30 min, counted from the refusal's `at` in ++ the journal, so a restart does not shorten it. After 5 of them for one ++ decision, at least 2 hours apart end to end, 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 decisions 72 and 73). ++- Recovery after a give-up is manual. Fixing the binding does not resend ++ the DM, which stays given up. The decision stays open in `mosaic inbox`, ++ the digest lists it, and the operator decides it with `mosaic decide`. - 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 @@ -35,20 +51,21 @@ index 589078c3..67dc6618 100644 - 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`, ++ `decision` a string (required for `dm`, null for `digest`), `day` a real ++ `YYYY-MM-DD` date (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. ++ not yours, a symlink (dangling or not) or not writable, or cannot be ++ created (for example, a parent is a file). It also refuses when ++ `sent.jsonl` is a symlink, a directory or not writable, or 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 +index 159160c0..083f8f1d 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 @@ -56,22 +73,36 @@ index 159160c0..26553cba 100644 notify.kill("SIGTERM"); await ended(notify, 5000); - broker.send({ op: "close" }); -+ if (broker.connected) 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" }); ++ 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); } +@@ -181,11 +181,11 @@ export async function startHost({ boot, business, notifier = null, bootTimeoutMs + closing = true; + let result = code; + if (notify) { +- if (notify.connected) notify.send({ op: "stop" }); ++ if (notify.connected) notify.send({ op: "stop" }, () => {}); + if ((await ended(notify, CLOSE_TIMEOUT_MS)) !== 0 && result === 0) result = 1; + } + await queue; +- if (broker.connected) broker.send({ op: "close" }); ++ if (broker.connected) broker.send({ op: "close" }, () => {}); + if ((await ended(broker, CLOSE_TIMEOUT_MS)) !== 0 && result === 0) result = 1; + const now = readHostState(dataRoot); + if (now && now.pid === state.pid && now.startTime === state.startTime) rmSync(hostFile(dataRoot), { force: true }); diff --git a/packages/cli/src/notifier.mjs b/packages/cli/src/notifier.mjs -index a4bb78d2..0610e1f5 100644 +index a4bb78d2..2a392b83 100644 --- a/packages/cli/src/notifier.mjs +++ b/packages/cli/src/notifier.mjs -@@ -4,17 +4,22 @@ +@@ -4,17 +4,25 @@ // bus; its memory is the journal `/notify//sent.jsonl` // (0600, in a 0700 directory), one line per send attempt: // @@ -83,10 +114,13 @@ index a4bb78d2..0610e1f5 100644 // 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. ++// decision 72): the next try waits the full 30 min, counted from the ++// refusal's line so a restart cannot shorten it (lead decision 73), and ++// after five for one decision, counted from the journal, the notifier ++// appends one `gave-up` line, logs once and stops sending that DM. Five ++// tries span two hours, long enough to fix a binding or token typo. The ++// digest marks it "DM refused, not retried"; recovery is the operator ++// deciding it. 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. @@ -96,7 +130,7 @@ index a4bb78d2..0610e1f5 100644 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; +@@ -25,6 +33,7 @@ export const POLL_MS = 30000; const BACKOFF_MS = 30000; const BACKOFF_MAX_MS = 30 * 60 * 1000; const LIMIT = 2000; @@ -104,7 +138,7 @@ index a4bb78d2..0610e1f5 100644 export const journalPath = (dataRoot, business) => join(dataRoot, "notify", business, "sent.jsonl"); -@@ -62,14 +68,14 @@ export function dmContent(business, d) { +@@ -62,14 +71,14 @@ export function dmContent(business, d) { return clip(lines.join("\n"), LIMIT); } @@ -121,12 +155,13 @@ index a4bb78d2..0610e1f5 100644 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) { +@@ -116,9 +125,32 @@ function copyTorn(dir, bytes, date) { } } +const OUTCOMES = ["confirmed", "refused", "unknown", "gave-up"]; +const DAY = /^\d{4}-\d{2}-\d{2}$/; ++const isDay = (d) => typeof d === "string" && DAY.test(d) && !Number.isNaN(Date.parse(d)) && new Date(d).toISOString().startsWith(d); +const UNWRITABLE = ["EACCES", "EPERM", "EROFS"]; + +// The first field of a journal record that has the wrong type, or null @@ -136,7 +171,7 @@ index a4bb78d2..0610e1f5 100644 + 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 (r.kind === "digest" ? !isDay(r.day) : r.day !== undefined && !isDay(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"; @@ -155,7 +190,7 @@ index a4bb78d2..0610e1f5 100644 // 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) { +@@ -126,20 +158,49 @@ function copyTorn(dir, bytes, date) { // both; the second copy is harmless. export function openJournal(file, { log = () => {}, now = () => new Date() } = {}) { const dir = dirname(file); @@ -163,7 +198,9 @@ index a4bb78d2..0610e1f5 100644 + 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); ++ // A dangling link in place of the directory fails mkdir with ENOENT. ++ if (e.code === "ENOENT" && lstatSync(dir, { throwIfNoEntry: false })?.isSymbolicLink()) throw new CliError(`notify journal directory must not be a symlink: ${dir}`, 3); ++ if ([...UNWRITABLE, "ENOTDIR"].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); @@ -183,15 +220,20 @@ index a4bb78d2..0610e1f5 100644 } 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); ++ if (e.code === "EISDIR") throw new CliError(`notify journal must be a regular file, mode 0600, owned by this user: ${file}`, 3); throw e; } const sent = new Set(); const days = new Set(); + const refusals = new Map(); + const gaveUp = new Set(); ++ const refusedAt = new Map(); + 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 (definite(r)) { ++ refusals.set(r.decision, (refusals.get(r.decision) ?? 0) + 1); ++ refusedAt.set(r.decision, Date.parse(r.at)); ++ } + if (r.outcome !== "confirmed") return; + if (r.kind === "dm") sent.add(r.decision); + else days.add(r.day); @@ -199,7 +241,7 @@ index a4bb78d2..0610e1f5 100644 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() } = { +@@ -156,12 +217,9 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { } catch { r = null; } @@ -215,16 +257,17 @@ index a4bb78d2..0610e1f5 100644 }); if (end < bytes.length) { const name = copyTorn(dir, bytes.subarray(end), now()); -@@ -176,6 +223,8 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { +@@ -176,6 +234,9 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { return { sent, days, + refusals, + gaveUp, ++ refusedAt, 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() } = { +@@ -183,9 +244,7 @@ export function openJournal(file, { log = () => {}, now = () => new Date() } = { } finally { closeSync(afd); } @@ -235,7 +278,7 @@ index a4bb78d2..0610e1f5 100644 }, }; } -@@ -202,6 +249,13 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => +@@ -202,6 +261,13 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => return b !== undefined && t < b.next; } @@ -249,18 +292,27 @@ index a4bb78d2..0610e1f5 100644 async function attempt(key, record, message) { const t = now().getTime(); try { -@@ -215,6 +269,10 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => +@@ -212,10 +278,16 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => + } catch (err) { + const kind = err?.kind === "refused" ? "refused" : "unknown"; + const status = Number.isInteger(err?.details?.status) ? err.details.status : null; ++ const line = { at: new Date(t).toISOString(), ...record, outcome: kind, messageId: null, ...(status !== null ? { status } : {}) }; 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 } : {}) }); +- 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 } : {}) }); +- log(`notify: ${record.kind} ${kind}${status !== null ? ` (HTTP ${status})` : ""}; retry in ${Math.round(Math.min(BACKOFF_MS * 2 ** n, BACKOFF_MAX_MS) / 1000)} s`); ++ const wait = definite(line) ? BACKOFF_MAX_MS : Math.min(BACKOFF_MS * 2 ** n, BACKOFF_MAX_MS); ++ backoff.set(key, { n, next: t + wait }); ++ journal.append(line); + 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`); ++ log(`notify: ${record.kind} ${kind}${status !== null ? ` (HTTP ${status})` : ""}; retry in ${Math.round(wait / 1000)} s`); return false; } -@@ -232,7 +290,12 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => + } +@@ -232,7 +304,15 @@ export function createNotifier({ business, dataRoot, inbox, direct, now = () => } const t = now(); for (const d of list) { @@ -271,10 +323,13 @@ index a4bb78d2..0610e1f5 100644 + giveUp(d.id, t.getTime()); + continue; + } ++ // A definite refusal waits the full cap from its journal line, which ++ // the in-memory backoff loses on a restart. ++ if (t.getTime() < (journal.refusedAt.get(d.id) ?? -Infinity) + BACKOFF_MAX_MS) 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 = () => +@@ -241,7 +321,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())) { @@ -284,7 +339,7 @@ index a4bb78d2..0610e1f5 100644 else done.failed++; } diff --git a/packages/cli/tests/host.test.mjs b/packages/cli/tests/host.test.mjs -index 8d20b47f..649031ae 100644 +index 8d20b47f..f8f11ec9 100644 --- a/packages/cli/tests/host.test.mjs +++ b/packages/cli/tests/host.test.mjs @@ -1,6 +1,7 @@ @@ -295,7 +350,7 @@ index 8d20b47f..649031ae 100644 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) { +@@ -48,6 +49,70 @@ async function until(fn, ms = 8000) { throw new Error("timed out waiting"); } @@ -323,11 +378,50 @@ index 8d20b47f..649031ae 100644 + t.after(() => unsubscribe("child_process", onChild)); + return sent; +} ++ ++// Fails this process's sends to new children the way node fails a write to a ++// child that has just died: `fake(child, m)` returns an error to fail that ++// send (to its callback if it has one, otherwise as an 'error' event on the ++// next tick), or nothing to send it for real. Deterministic where the real ++// window after a SIGKILL is a millisecond wide (Darkwing R1, lead decision 73). ++// Every child it saw is killed after the test, so a host left half closed ++// fails the test rather than hang it. ++function failSends(t, fake) { ++ const children = []; ++ const onChild = ({ process: child }) => { ++ children.push(child); ++ let send; ++ Object.defineProperty(child, "send", { ++ configurable: true, ++ get: () => send, ++ set(fn) { ++ send = function (m, ...rest) { ++ const err = fake(this, m); ++ if (!err) return fn.call(this, m, ...rest); ++ const callback = rest.find((a) => typeof a === "function"); ++ if (callback) process.nextTick(callback, err); ++ else process.nextTick(() => this.emit("error", err)); ++ return false; ++ }; ++ }, ++ }); ++ }; ++ subscribe("child_process", onChild); ++ t.after(() => { ++ unsubscribe("child_process", onChild); ++ for (const child of children) child.kill("SIGKILL"); ++ }); ++} ++ ++const epipe = (child) => { ++ child.kill("SIGKILL"); ++ return Object.assign(new Error("write EPIPE"), { code: "EPIPE", errno: -32, syscall: "write" }); ++}; + 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 +@@ -64,8 +129,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 = []; @@ -340,7 +434,7 @@ index 8d20b47f..649031ae 100644 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 +@@ -84,12 +153,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)}`)); @@ -362,7 +456,7 @@ index 8d20b47f..649031ae 100644 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 +@@ -107,6 +180,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) }); @@ -370,7 +464,7 @@ index 8d20b47f..649031ae 100644 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 +@@ -114,6 +188,21 @@ test("a notifier that dies takes the host down with exit 1, so the unit restarts assert.equal(existsSync(hostFile(f.dataRoot)), false); }); @@ -392,7 +486,7 @@ index 8d20b47f..649031ae 100644 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" +@@ -127,9 +216,111 @@ test("a notifier that refuses stops the broker and the host refuses with exit 3" assert.equal(existsSync(hostFile(f.dataRoot)), false); }); @@ -447,6 +541,49 @@ index 8d20b47f..649031ae 100644 + assert.deepEqual(errors, [], "no close was sent over the closed channel"); + assert.equal(sends.filter((m) => m?.op === "close").length, 0); +}); ++ ++test("a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3", 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 }); ++ failSends(t, (child, m) => (m?.op === "close" ? epipe(child) : null)); ++ 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 && /^notifier refused to start: .*no dmRecipient/.test(e.message)); ++}); ++ ++test("a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error", 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 }); ++ failSends(t, (child, m) => { ++ if (m?.op === "close") return epipe(child); ++ if (m?.op === "start") { ++ // Stopped, it cannot reply; then it dies. ++ child.kill("SIGSTOP"); ++ setImmediate(() => child.kill("SIGKILL")); ++ } ++ return null; ++ }); ++ 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)); ++}); ++ ++test("close() whose stop and close sends fail with EPIPE still finishes, with exit 1", async (t) => { ++ const root = tmp(t); ++ const f = fixture(root); ++ makeDeployment(root, { dmRecipient: IDS.owner }); ++ const discord = await fakeDiscord(t); ++ const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env }); ++ failSends(t, (child, m) => (m?.op === "stop" || m?.op === "close" ? epipe(child) : null)); ++ const host = await startHost({ boot, business: "acme", notifier: { binding: "test-seat", base: discord.base, pollMs: 100 }, log: () => {} }); ++ // No t.after close: a close() that rejected leaves `done` pending forever. ++ assert.equal(await host.close(0), 1, "both children died by signal"); ++ assert.equal(existsSync(hostFile(f.dataRoot)), false); ++}); + 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" }); @@ -461,8 +598,16 @@ index 8d20b47f..649031ae 100644 const late = spawn(process.execPath, ["-e", "setTimeout(() => {}, 60000)"], { stdio: "ignore" }); t.after(() => late.kill("SIGKILL")); await once(late, "spawn"); +@@ -215,6 +406,7 @@ test("bus-service.sh renders the unit and installs it into a given directory", ( + const first = spawnSync(script, ["install", "--dir", dir, "--no-reload"], { encoding: "utf8" }); + assert.equal(first.status, 0, first.stderr); + assert.match(first.stdout, /written: /); ++ assert.match(first.stdout, /^ {2}mkdir -m 0700 -p \/notify\/ +the notifier refuses a looser directory$/m); + assert.equal(readFileSync(join(dir, "mosaic-bus@.service"), "utf8"), render.stdout); + assert.match(spawnSync(script, ["install", "--dir", dir, "--no-reload"], { encoding: "utf8" }).stdout, /unchanged: /); + assert.match(spawnSync(script, ["uninstall", "--dir", dir, "--no-reload"], { encoding: "utf8" }).stdout, /removed: /); diff --git a/packages/cli/tests/notifier.test.mjs b/packages/cli/tests/notifier.test.mjs -index 99127662..aa81ac69 100644 +index 99127662..3b65af47 100644 --- a/packages/cli/tests/notifier.test.mjs +++ b/packages/cli/tests/notifier.test.mjs @@ -1,12 +1,15 @@ @@ -472,7 +617,7 @@ index 99127662..aa81ac69 100644 +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 { createNotifier, digestContent, digestNonce, dmNonce, journalPath, openJournal, POLL_MS, REFUSAL_LIMIT, runLoop, zoned } from "../src/notifier.mjs"; import { RestOutcome } from "../../discord/src/rest.mjs"; import { broker, tmp } from "./helpers.mjs"; @@ -493,10 +638,25 @@ index 99127662..aa81ac69 100644 }, }; } -@@ -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))); - }); - +@@ -91,14 +95,117 @@ test("a failed DM is journaled, backs off, and is retried until it lands", async + assert.equal((await s.notifier.tick()).failed, 0, "inside the first 30 s backoff"); + s.time.advance(25_000); + assert.equal((await s.notifier.tick()).failed, 1, "second attempt refused"); +- s.time.advance(45_000); +- assert.equal((await s.notifier.tick()).dms, 0, "inside the 60 s backoff"); +- s.time.advance(20_000); ++ s.time.advance(29 * 60_000); ++ assert.equal((await s.notifier.tick()).dms, 0, "a definite refusal waits the full 30 min, not the 60 s step"); ++ s.time.advance(60_000); + assert.equal((await s.notifier.tick()).dms, 1); + assert.deepEqual(s.journal().map((r) => r.outcome), ["unknown", "refused", "confirmed"]); + assert.equal(s.journal()[1].status, 403); + assert.equal(new Set(s.direct.sends.map((m) => m.nonce)).size, 1, "every retry reuses the nonce"); +- assert.ok(s.logs.some((l) => /retry in 30 s/.test(l))); ++ assert.ok(s.logs.some((l) => /unknown; retry in 30 s/.test(l))); ++ assert.ok(s.logs.some((l) => /refused \(HTTP 403\); retry in 1800 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; @@ -563,10 +723,44 @@ index 99127662..aa81ac69 100644 + assert.equal(s.journal().length, REFUSAL_LIMIT + 1, "one gave-up line only"); +}); + ++// Polls every POLL_MS from the current clock up to `until` ms later; returns ++// the minute (from `from`) of each new send. ++async function pollUntil(s, notifier, from, until, onPoll = () => {}) { ++ const minutes = []; ++ for (let elapsed = s.time.clock.t.getTime() - from; elapsed <= until; elapsed += POLL_MS) { ++ const before = s.direct.sends.length; ++ await notifier.tick(); ++ if (s.direct.sends.length > before) minutes.push(elapsed / 60_000); ++ onPoll(elapsed); ++ s.time.advance(POLL_MS); ++ } ++ return minutes; ++} ++ ++test("polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then", async (t) => { ++ const s = setup(t, MIDNIGHT, Array(10).fill("refused")); ++ s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); ++ const from = s.time.clock.t.getTime(); ++ const gaveUp = () => s.journal().some((r) => r.outcome === "gave-up"); ++ const minutes = await pollUntil(s, s.notifier, from, 120 * 60_000 + POLL_MS, (elapsed) => { ++ assert.equal(gaveUp(), elapsed >= 120 * 60_000, `gave-up line at +${elapsed / 1000} s`); ++ }); ++ assert.deepEqual(minutes, [0, 30, 60, 90, 120]); ++ assert.equal(s.logs.filter((l) => /refused \(HTTP 403\); retry in 1800 s/.test(l)).length, 4); ++}); ++ ++test("a restart after the second refusal does not send before that refusal's 30 min are up", async (t) => { ++ const s = setup(t, MIDNIGHT, Array(10).fill("refused")); ++ s.raise("git.push.protected", { target: "refactor", blocking: true, task_ref: "vikunja:1/7" }); ++ const from = s.time.clock.t.getTime(); ++ assert.deepEqual(await pollUntil(s, s.notifier, from, 30 * 60_000), [0, 30]); ++ s.time.advance(60_000); ++ const after = s.make(); ++ assert.deepEqual(await pollUntil(s, after, from, 60 * 60_000), [60], "nothing between the restart and +60 min"); + }); + 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'; +@@ -150,7 +257,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"); @@ -575,7 +769,7 @@ index 99127662..aa81ac69 100644 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 +@@ -164,7 +271,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/); @@ -584,7 +778,7 @@ index 99127662..aa81ac69 100644 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 +@@ -172,7 +279,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"); @@ -593,7 +787,7 @@ index 99127662..aa81ac69 100644 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 +@@ -182,7 +289,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); @@ -602,7 +796,7 @@ index 99127662..aa81ac69 100644 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 +@@ -222,6 +329,108 @@ 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)); }); @@ -622,6 +816,8 @@ index 99127662..aa81ac69 100644 + ["decision", { ...digest, decision: "a" }], + ["day", { ...digest, day: undefined }], + ["day", { ...digest, day: "2026-10-8" }], ++ ["day", { ...digest, day: "2026-13-45" }], ++ ["day", { ...digest, day: "2026-02-30" }], + ["day", { ...dm, day: 20261008 }], + ["outcome", { ...dm, outcome: "sent" }], + ["outcome", { ...digest, outcome: "gave-up", messageId: null }], @@ -656,6 +852,20 @@ index 99127662..aa81ac69 100644 + assert.deepEqual(readdirSync(real), [], "nothing was created through the link"); +}); + ++test("the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3", (t) => { ++ const root = tmp(t); ++ const dangling = journalPath(root, "demo"); ++ mkdirSync(dirname(dirname(dangling)), { mode: 0o700 }); ++ symlinkSync(join(root, "missing"), dirname(dangling)); ++ assert.throws(() => openJournal(dangling), (e) => e.exitCode === 3 && e.message === `notify journal directory must not be a symlink: ${dirname(dangling)}`); ++ const under = join(root, "plain", "notify", "demo", "sent.jsonl"); ++ writeFileSync(join(root, "plain"), ""); ++ assert.throws(() => openJournal(under), (e) => e.exitCode === 3 && e.message === `notify journal directory cannot be created (ENOTDIR): ${dirname(under)}`); ++ const asDir = journalPath(root, "acme"); ++ mkdirSync(asDir, { recursive: true, mode: 0o700 }); ++ assert.throws(() => openJournal(asDir), (e) => e.exitCode === 3 && e.message === `notify journal must be a regular file, mode 0600, owned by this user: ${asDir}`); ++}); ++ +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"); @@ -679,6 +889,9 @@ index 99127662..aa81ac69 100644 + } finally { + chmodSync(dirname(file), 0o700); + } ++ openJournal(file); ++ chmodSync(file, 0o400); ++ assert.throws(() => openJournal(file), (e) => e.exitCode === 3 && e.message === `notify journal is not writable (EACCES): ${file}`); + const parent = join(root, "notify"); + const other = journalPath(root, "acme"); + chmodSync(parent, 0o500); diff --git a/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 b/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 index f66cb220..84e70f06 100644 --- a/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 +++ b/agents/rocko/work/s4-follow-up/candidate-manifest.sha256 @@ -1,8 +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 +33835702af268d8337c68a9d62ab7340174ec6ec990a86422c099a922feba61d packages/cli/README.md +24461cccd45d08cf4b5b77d2b44bd1792002bcaca028844b1f0f013052d097d6 packages/cli/src/host.mjs +cb27929bd2d17704ff7db42a0b001d97cb626ec79fa833add4bdaff2e304a4c3 packages/cli/src/notifier.mjs +b998202b4872c4929b1aeb601ed26da4bdaef8549ae0676ce650a23fa4f0f38c packages/cli/tests/host.test.mjs +38d499cf3f2a0b78fb08d473946ea6517cc8a6b35681bd077695ddb507fb3cc6 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/mutants.sh b/agents/rocko/work/s4-follow-up/mutants.sh index 14ca568f..45445218 100755 --- a/agents/rocko/work/s4-follow-up/mutants.sh +++ b/agents/rocko/work/s4-follow-up/mutants.sh @@ -1,6 +1,8 @@ #!/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 +# J4 and the new error paths. Round 2 adds the send callbacks (R1), the +# refusal spacing and restart (R2, lead decision 73), X9, X14, the day check +# and the error paths from Darkwing's note 1. 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. @@ -38,3 +40,18 @@ run J4c $NT 's/if \(r\.status !== undefined && !Number\.isInteger\(r\.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//' +BS=scripts/bus-service.sh +# Round 2. +run G144cb $HS 's/(notify\.kill\("SIGTERM"\);\n\s*await ended\(notify, 5000\);\n\s*if \(broker\.connected\) broker\.send\(\{ op: "close" \}), \(\) => \{\}\)/$1)/' +run G150cb $HS 's/(if \(ok\?\.ok !== true\) \{\n\s*await ended\(notify, 5000\);\n\s*if \(broker\.connected\) broker\.send\(\{ op: "close" \}), \(\) => \{\}\)/$1)/' +run G184 $HS 's/notify\.send\(\{ op: "stop" \}, \(\) => \{\}\)/notify.send({ op: "stop" })/' +run G188 $HS 's/(await queue;\n\s*if \(broker\.connected\) broker\.send\(\{ op: "close" \}), \(\) => \{\}\)/$1)/' +run R2a $NT 's/const wait = definite\(line\) \? BACKOFF_MAX_MS : /const wait = /' +run R2b $NT 's/ if \(t\.getTime\(\) < \(journal\.refusedAt\.get\(d\.id\)[^\n]*\n//' +run R2c $NT 's/ refusedAt\.set\(r\.decision, Date\.parse\(r\.at\)\);\n//' +run X9 $NT 's/ if \(UNWRITABLE\.includes\(e\.code\)\) throw new CliError\(`notify journal is not writable[^\n]*\n//' +run X14 $BS 's/ mkdir -m 0700 -p \/notify\/[^\n]*\n//' +run D3 $NT 's/ && !Number\.isNaN\(Date\.parse\(d\)\) && new Date\(d\)\.toISOString\(\)\.startsWith\(d\)//' +run N1a $NT 's/ if \(e\.code === "ENOENT" && lstatSync\(dir[^\n]*\n//' +run N1b $NT 's/\[\.\.\.UNWRITABLE, "ENOTDIR"\]/UNWRITABLE/' +run N1c $NT 's/ if \(e\.code === "EISDIR"\)[^\n]*\n//' diff --git a/agents/rocko/work/s4-follow-up/out/gate-r2-end.txt b/agents/rocko/work/s4-follow-up/out/gate-r2-end.txt new file mode 100644 index 00000000..d277637c --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/gate-r2-end.txt @@ -0,0 +1,2 @@ +2.44 1.79 2.16 1/12979 779335 +2026-10-09T14:05:56Z diff --git a/agents/rocko/work/s4-follow-up/out/gate-r2-exits.txt b/agents/rocko/work/s4-follow-up/out/gate-r2-exits.txt new file mode 100644 index 00000000..b2080999 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/gate-r2-exits.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 0 +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/gate-r2-start.txt b/agents/rocko/work/s4-follow-up/out/gate-r2-start.txt new file mode 100644 index 00000000..b5d65a57 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/gate-r2-start.txt @@ -0,0 +1,2 @@ +1.01 0.95 2.02 7/12984 683241 +2026-10-09T14:02:34Z diff --git a/agents/rocko/work/s4-follow-up/out/manifest-check-tree-r2.txt b/agents/rocko/work/s4-follow-up/out/manifest-check-tree-r2.txt new file mode 100644 index 00000000..25064faa --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/manifest-check-tree-r2.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/node-bus-r2.txt b/agents/rocko/work/s4-follow-up/out/node-bus-r2.txt new file mode 100644 index 00000000..001abf13 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-bus-r2.txt @@ -0,0 +1,75 @@ +✔ launch identity is stamped, payload identity is refused and stale holder cannot send (20.217177ms) +✔ decision classes route from policy; gated resolution is human-only, choice and target must match (23.913734ms) +✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (27.418015ms) +✔ launch events require a human CLI capability; generic emit cannot forge authority events (20.095476ms) +✔ within-role decisions close atomically and invalid options or blocking omissions refuse (15.028242ms) +✔ observer capabilities read human inbox but cannot mutate or forge launch identity (13.46164ms) +✔ task action subjects and linked decision trail are complete and ordered (20.841526ms) +✔ launch binding is durable and reconnecting requires the identical trusted record (8.013879ms) +✔ business isolation includes inherited object names and cross-business message references (14.650376ms) +✔ authority never transfers between action, run, target, unresolved or replaced role holder (18.622725ms) +✔ task projection uses schema current view, skipping earlier and equal-start polls (9.427439ms) +✔ revocation permanently bars the old run from reclaiming first, including after broker restart (15.623429ms) +✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (10.401472ms) +✔ both arbiters require human resolution when their cross-role route is themselves (15.860762ms) +✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (1.726404ms) +✔ only validated broker references load; returned data and exceptions cannot expose a known token (3.477977ms) +✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (2.348156ms) +✔ expiry refuses use and env references never become client data (0.808375ms) +✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.64772ms) +✔ opaque tokens shorter than 16 characters refuse before use (0.37342ms) +✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.132056ms) +✔ process reader gets own kernel identity without exposing environment values (1.287166ms) +✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.788136ms) +✔ real pid 1 remains inspectable when its environment is protected (0.359006ms) +✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (24.288305ms) +✔ missing and foreign-business citations refuse and roll back message and grant (17.011547ms) +✔ cross-role sends still need a matching resolved decision and consume it once (28.381359ms) +✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (66.723278ms) +✔ startup token refusal returns safe code without value or partial listening broker (38.97226ms) +✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (49.827986ms) +✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (82.849468ms) +✔ trusted host registers later launches; socket clients never have a registration verb (54.101581ms) +✔ runtime excludes declared project roots even when host supplies no repoRoots (37.386095ms) +✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (48.361311ms) +✔ v3b prototype refusals, views and append-only mutations (82.523597ms) +✔ gated approval authorizes once, survives store reopen, and fresh approval works (27.788071ms) +✔ another run cannot consume an approval; a failed check leaves it usable (20.309447ms) +✔ two scheduled callers have exactly one grant and one consumed refusal (20.492887ms) +✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (29.50709ms) +✔ class drift gated to cross-role refuses before consumption (19.187248ms) +✔ class drift cross-role to gated refuses before consumption (14.978956ms) +✔ class drift gated to within-role refuses before consumption (16.443241ms) +✔ class drift cross-role to within-role refuses before consumption (16.072617ms) +✔ class drift within-role to gated refuses before consumption (15.064451ms) +✔ class drift within-role to cross-role refuses before consumption (15.61633ms) +✔ message.send consumes approval and prevents a later send or authorize (15.913262ms) +✔ role.revoke consumes approval and prevents a later revoke or authorize (16.694773ms) +✔ creates private WAL store and excludes a second writer until explicit close (15.558522ms) +✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (15.837274ms) +✔ existing empty database and symlink runtime directory refuse, never initialize over damage (15.430354ms) +✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (51.488693ms) +✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (9.684961ms) +✔ async transactions refuse before invoking their function (7.964162ms) +✔ recordTask keeps sync reads and a role write apart (20.233791ms) +✔ read_at must be one canonical UTC format, so the projection compares strings safely (9.909358ms) +✔ a bad entry refuses the whole record (9.524417ms) +✔ taskView reads the projection for one business (16.739565ms) +✔ requestTask hands only a holder and a task verb to the handler, and records refusals (21.811716ms) +✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (269.121186ms) +✔ without an adapter the server refuses every task verb (15.529542ms) +✔ the runtime refuses an invalid adapter and closes a valid one (14.405829ms) +✔ the process loads the S3 adapter from plain-data trackers (87.38528ms) +✔ socket capability stamps launch identity; shared views use wire, no SQL client (24.374712ms) +✔ two wire claims serialize; a lost reply never automatically retries (18.879726ms) +✔ malformed, oversized and identity-forging envelopes refuse without echoing input (16.264802ms) +✔ client preserves UTF-8 when a response divides a multibyte character (11.772894ms) +✔ committed mutation followed by dropped reply reports unknown and is never retried (13.790622ms) +ℹ tests 67 +ℹ suites 0 +ℹ pass 67 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 577.642302 diff --git a/agents/rocko/work/s4-follow-up/out/node-business-r2.txt b/agents/rocko/work/s4-follow-up/out/node-business-r2.txt new file mode 100644 index 00000000..1512e2c7 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-business-r2.txt @@ -0,0 +1,68 @@ +✔ config directory and file path follow MOSAIC_CONFIG (1.452137ms) +✔ the fixture business validates and comes back frozen (5.578344ms) +✔ two instances may share a definition (1.441253ms) +✔ top-level refusals (5.152839ms) +✔ arbiters and projects (7.563057ms) +✔ role instances (3.855653ms) +✔ Vikunja bots (9.49824ms) +✔ a role without Vikunja takes no tracker block (3.98548ms) +✔ credential references match the definition's services (5.343946ms) +✔ launch (10.588077ms) +✔ loadBusiness: file checks (3.090109ms) +✔ loadBusiness: not a regular file (42.83915ms) +✔ loading writes nothing (1.21552ms) +✔ names that are Object.prototype properties don't count as declared (2.449986ms) +✔ the shipped example refuses as written and validates once filled in (1.466161ms) +✔ usage errors exit 4 (285.939347ms) +✔ validate: a good business exits 0 and prints instance digests (65.835211ms) +✔ validate: project files (316.787862ms) +✔ validate: missing files and a broken system config (242.410619ms) +✔ validate: credential reference problems exit 2 and name each one (66.429598ms) +✔ validate: a token file inside the repository is refused (61.975689ms) +✔ validate: role definitions come from MOSAIC_ROLES_DIR (189.828413ms) +✔ resolve: prints one instance's record (192.088605ms) +✔ resolve: refusals (370.817334ms) +✔ parse: exactly one of file or env, plus the service's date (2.912184ms) +✔ check: a good file has no problems (0.697025ms) +✔ check never opens the file: a write-only token passes (0.352511ms) +✔ check: file problems (0.754102ms) +✔ check: token files can't live in the repository or dataRoot, even through a linked directory (0.720844ms) +✔ check: dates and environment references (0.368981ms) +✔ path and load (2.114907ms) +✔ refusals (1.225659ms) +✔ systemVars flattens the validated config (2.575859ms) +✔ precedence: system, business, project, project role, agent (8.530151ms) +✔ limits narrow the definition and never widen it (3.350867ms) +✔ role.launch stays within-role only for the instance the launch block names (7.962531ms) +✔ limits.authority without role.launch leaves the launcher with no launch block (2.886369ms) +✔ limits.authority narrows cross-role actions too (2.092685ms) +✔ classify (2.292167ms) +✔ the record carries what the broker and launcher need (1.011055ms) +✔ digest: key order doesn't matter, any value change does (7.193711ms) +✔ refusals (2.432054ms) +✔ the four shipped version 2 roles load (3.955928ms) +✔ shipped role scopes match addendum B section 2 and the SR runbook (1.776154ms) +✔ shipped authority follows the note's table (1.111235ms) +✔ version 1 files keep loading with no authority (1.386411ms) +✔ the conductor policy isn't a role (0.428507ms) +✔ a missing role file is exit 4, a symbolic link too (0.540084ms) +✔ version 2 refusals (5.32113ms) +✔ authority: closed vocabulary, no gated-only action, no overlap (3.077258ms) +✔ credentials: Gitea scopes (1.661659ms) +✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (2.161649ms) +✔ credentials: services (0.922355ms) +✔ contract: a non-empty regular Markdown file beside the role file (0.714248ms) +✔ every key names known layers and a merge rule (1.083157ms) +✔ unknown keys and wrong layers refuse (0.688039ms) +✔ types (2.09786ms) +✔ merge: defaults, then the most specific layer wins (0.434102ms) +✔ merge: limits only narrow, and provenance lists each source (0.444915ms) +✔ merge doesn't change its inputs (0.176633ms) +ℹ tests 60 +ℹ suites 0 +ℹ pass 60 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 1866.960358 diff --git a/agents/rocko/work/s4-follow-up/out/node-cli-r2.txt b/agents/rocko/work/s4-follow-up/out/node-cli-r2.txt new file mode 100644 index 00000000..3db343a4 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-cli-r2.txt @@ -0,0 +1,76 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (15.406365ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (19.889859ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (11.082826ms) +✔ decide prints a declining choice as declining (10.900547ms) +✔ an unknown outcome is reported once and never resent (9.993144ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (9.741018ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (11.82987ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (10.354325ms) +✔ every human command refuses inside an agent run before it touches the bus (9.997008ms) +✔ usage errors exit 4; no business and no host is a usage error (10.647727ms) +✔ agents and tasks print through the broker (10.313741ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.332501ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (39.17658ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (33.4277ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (28.461035ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (33.735066ms) +✔ a business without tracker.baseUrl gets no trackers entry (40.658634ms) +✔ an unknown business and a broken system config refuse with exit 3 (62.480257ms) +✔ empty views say so (0.745845ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (0.7702ms) +✔ tasks print the tracker fields the snapshot carries (0.123489ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (861.401799ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (134.37757ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (72.16873ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.331709ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (118.368837ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (78.525932ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (123.149211ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (76.627673ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (130.111723ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.373132ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.144574ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (204.506438ms) +✔ bus start refuses with exit 3 without a notifier config (80.300161ms) +✔ bus start runs until bus stop; status reports it while it runs (589.870543ms) +✔ bus-service.sh renders the unit and installs it into a given directory (28.306311ms) +✔ zoned uses the IANA zone across DST (12.414673ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (15.361146ms) +✔ two blocking decisions get two DMs with different nonces (12.226799ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.171141ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (10.006057ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (13.970726ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (15.636064ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (11.408405ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (99.487844ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (42.033245ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (12.180808ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (6.843471ms) +✔ an inbox read failure is logged and the next poll retries (0.566595ms) +✔ no Discord id reaches the journal or the log (7.102793ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.680487ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (1.967108ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.242875ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.399269ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.47903ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.788018ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.28774ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.448896ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.329134ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.398665ms) +✔ digest content stays within Discord's 2000 characters (0.232068ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.778579ms) +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 (253.53672ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (29.875889ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2148.77842ms) +✔ busExit and refuseInsideAgent (0.531925ms) +ℹ tests 66 +ℹ suites 0 +ℹ pass 66 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2933.537466 diff --git a/agents/rocko/work/s4-follow-up/out/node-control-board-r2.txt b/agents/rocko/work/s4-follow-up/out/node-control-board-r2.txt new file mode 100644 index 00000000..452e1736 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-control-board-r2.txt @@ -0,0 +1,132 @@ +✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (3.621501ms) +✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.239892ms) +✔ completed smoke replies and ordinary questions are idle, not human blockers (0.725294ms) +✔ only an explicit first-line input request makes a finished reply waiting (0.150697ms) +✔ tool activity, user text, errors and unfinished turns override attention text (0.122357ms) +✔ STOP access failure is unknown, not absence, under a non-root identity (34.557499ms) +✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.106752ms) +✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.34525ms) +✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (5.383925ms) +✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.560572ms) +✔ server rescans connector discovery and refuses HTTP reply without transport (30.899922ms) +✔ connector session links and linked directories are not read (1.012519ms) +✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (3.354834ms) +✔ CLI print uses the relaunch notice instead of old current preview (56.057003ms) +✔ connector owner and fixed task never inherit a native relaunch notice (1.818617ms) +✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.438209ms) +✔ loadConfig: missing file throws ConfigError (1.410362ms) +✔ loadConfig: invalid JSON throws ConfigError (0.27626ms) +✔ loadConfig: missing dataRoot throws ConfigError (0.206544ms) +✔ loadConfig: relative dataRoot throws ConfigError (0.198034ms) +✔ loadConfig: valid config returns dataRoot (0.604677ms) +✔ findNewestSession: picks the newest by mtime among two files (0.434068ms) +✔ findNewestSession: finds files in nested subdirectories (0.284813ms) +✔ findNewestSession: returns null for a missing dir (0.117471ms) +✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.587025ms) +✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.170303ms) +✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.419397ms) +✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (0.997913ms) +✔ deriveState: full state table (0.16249ms) +✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.304815ms) +✔ rule: newest entry is a tool result with no assistant text after it is working (0.260324ms) +✔ rule: a finished ordinary turn is idle, even if it says your move (0.245797ms) +✔ task: the first user message of the session, from text blocks (0.234725ms) +✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.237149ms) +✔ task: no user message in the log means null (shown as unknown), never a guess (0.240797ms) +✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.338475ms) +✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.465721ms) +✔ scan: the written record carries task, workspace and activeProject (0.455056ms) +✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.52772ms) +✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.347052ms) +✔ 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.94347ms) +✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.392815ms) +✔ loadRegistrations: a missing seatsDir gives empty lists (0.129706ms) +✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.66035ms) +✔ 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.29492ms) +✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.630719ms) +✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.297576ms) +✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.514123ms) +✔ scanAgent: ageSeconds is computed from the injected now (0.253019ms) +✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.149203ms) +✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.315371ms) +✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.439033ms) +✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.987942ms) +✔ scan: relative boardDir throws ConfigError (0.098999ms) +✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (54.602612ms) +✔ CLI: missing config exits 2 with a refused: message (49.586258ms) +✔ CLI: unknown command exits 2 (48.912305ms) +✔ CLI: unknown --liveness value exits 2 (51.658356ms) +✔ panesRunPi: true when any trimmed line equals 'pi' (0.19844ms) +✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.069606ms) +✔ tmuxIsAlive: a pane running pi is alive (0.181262ms) +✔ tmuxIsAlive: session exists but pi has exited is not alive (0.074403ms) +✔ tmuxIsAlive: no such tmux session is not alive (0.079362ms) +✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.069174ms) +✔ tmuxIsAlive: passes -L only when a socket is given (0.098014ms) +✔ parsePanes: one pane per line, command and optional tab-separated path (0.078915ms) +✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.078816ms) +✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.095051ms) +✔ loadSeen: missing file returns {} (0.159643ms) +✔ loadSeen: invalid JSON throws ConfigError (0.188476ms) +✔ loadSeen: a JSON array throws ConfigError (0.153238ms) +✔ loadSeen: a non-string value throws ConfigError (0.185738ms) +✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.376136ms) +✔ markSeen: seen false deletes the key (0.286277ms) +✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.206712ms) +✔ markSeen: project containing '/' throws ConfigError (0.119844ms) +✔ markSeen: non-boolean seen throws ConfigError (0.114391ms) +✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.297461ms) +✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.222805ms) +✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.224168ms) +✔ scanAgent: an error-state session with a matching mark is seen (0.239582ms) +✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.530782ms) +✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.201505ms) +✔ 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.657081ms) +✔ 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.555664ms) +✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (1.135361ms) +✔ isLoopbackHost: recognizes loopback hosts (1.193759ms) +✔ isLoopbackHost: rejects non-loopback hosts (4.175629ms) +✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (2.931585ms) +✔ startServer: serves page, healthz, and a rescanning /api/board (34.632692ms) +✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (5.469118ms) +✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.396976ms) +✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (51.22562ms) +✔ CLI: serve rejects a non-numeric --port with exit 2 (50.883701ms) +✔ CLI: scan still works after the async cli refactor (54.813228ms) +✔ CLI: live serve prints its URL and answers /healthz (58.398192ms) +✔ page.html: esc() escapes every HTML-significant character (0.659275ms) +✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.07859ms) +✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.037422ms) +✔ POST /api/seen with invalid JSON returns 400 (2.835152ms) +✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.019713ms) +✔ POST /api/seen with a missing agent returns 400 (1.295617ms) +✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.669646ms) +✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (50.449813ms) +✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.337992ms) +✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.270884ms) +✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.16532ms) +✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.13303ms) +✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.309982ms) +✔ 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.577047ms) +✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S :control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (28.948657ms) +✔ POST /api/reply: a registration with a tmux socket adds -L (25.686903ms) +✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (29.374986ms) +✔ POST /api/reply: refusals before the tool runs: empty or blank or long text 400, unknown row 404, no registration 409, stale registration 409, no tmux session 409, bad JSON 400; the tool is never called (14.264179ms) +✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.271884ms) +✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.155668ms) +✔ page.html: the reply box appears only where canReply() holds (live registration with a tmux session), the detail has a Reply row, the submit posts JSON to /api/reply, receipts and drafts survive a refresh, and every receipt value is escaped (0.584762ms) +✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.429038ms) +✔ 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.302417ms) +✔ 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.946952ms) +✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (28.283933ms) +✔ 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.489636ms) +✔ every refusal code the reader can raise has an HTTP status (2.556468ms) +✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (47.006525ms) +ℹ tests 124 +ℹ suites 0 +ℹ pass 124 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 616.847495 diff --git a/agents/rocko/work/s4-follow-up/out/node-conversation-r2.txt b/agents/rocko/work/s4-follow-up/out/node-conversation-r2.txt new file mode 100644 index 00000000..6f0c5543 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-conversation-r2.txt @@ -0,0 +1,160 @@ +✔ W1: two processes acquire the same pair at once; exactly one claim (138.999793ms) +✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (8.506643ms) +✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (3.652426ms) +✔ W2: acquire while a claim is reserved or active refuses already-active (139.65297ms) +✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (208.781861ms) +✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (146.913673ms) +✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (16.758077ms) +✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (15.265081ms) +✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.423659ms) +✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (5564.882556ms) +✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (21290.183026ms) +✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (148.837324ms) +✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (90.038212ms) +✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (222.59503ms) +✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (180.796554ms) +✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (211.80975ms) +✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (33.370238ms) +✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (95.958842ms) +✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (31.22442ms) +✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (90.183997ms) +✔ W11: the controller writes no session file; only the fake engine's own appends appear (22.32039ms) +✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (51.795196ms) +✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (57.941598ms) +✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.484511ms) +✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.066388ms) +✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.763114ms) +✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2549.276526ms) +✔ K2: K1 on the process-group fallback ends uncertain, never stopped (144.547438ms) +✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2428.985502ms) +✔ K4: two engines; force stop one; the other survives by independent observation (4301.9598ms) +✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2149.163268ms) +✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2246.94703ms) +✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2148.008577ms) +✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4339.87992ms) +✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (385.636316ms) +✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (320.160393ms) +✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (259.118506ms) +✔ K6: recover without proof, without confirmation, or with changed pins is refused (65.979593ms) +✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (30.958052ms) +✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (38.927223ms) +✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3025.311243ms) +✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (5.577284ms) +✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (31.074742ms) +✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (25.6351ms) +✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (32.684296ms) +✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (31.894859ms) +✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (25.404556ms) +✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (41.567832ms) +✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (18.393873ms) +✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.681211ms) +✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (23.740901ms) +✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (125.240068ms) +✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (40.541369ms) +✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (20.472634ms) +✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (30.998105ms) +✔ 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 (35.918921ms) +✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (11.250458ms) +✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (32.533965ms) +✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (47.276026ms) +✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (41.965175ms) +✔ terminal: engine control characters are made visible; a lost connection refuses submit (24.174742ms) +✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.562867ms) +✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.2942ms) +✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.422497ms) +✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.120017ms) +✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (332.391101ms) +✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (54.806462ms) +✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (82.43351ms) +✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (132.498415ms) +✔ H4: self-takeover is refused (20.051491ms) +✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (93.211892ms) +✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (87.129707ms) +✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (23.395035ms) +✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (78.376918ms) +✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (129.210058ms) +✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (14.867287ms) +✔ H13: a retry with the same request ID and different text is refused (15.797975ms) +✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (140.500735ms) +✔ H15: a revoked connection's command is refused; the revocation fence holds (70.40603ms) +✔ H16: a second controller for the same session refuses already-active; the first is untouched (20.607613ms) +✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (57.752007ms) +✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (59.76822ms) +✔ H18: two prompts before any native output: the second refuses busy; one engine write (15.350836ms) +✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (120.166377ms) +✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.694913ms) +✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (466.178154ms) +✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (359.880623ms) +✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (319.856375ms) +✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2390.814685ms) +✔ H23: requests pending at a restart are not resent; each shows outcome unknown (461.597177ms) +✔ a plain conversation: catalogue row, one page, CHAT-01 records (6.495309ms) +✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.845716ms) +✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.75288ms) +✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.618534ms) +✔ F1: an unreadable fork is never merged into another branch's history (1.689415ms) +✔ F1: a follow stays on its branch when the next entry's parent is unreadable (2.008519ms) +✔ F1: a file whose entries are all unreadable shows a notice per line (0.784078ms) +✔ F2: a truncated trailing line marks the view incomplete, not an error (1.223817ms) +✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.751945ms) +✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.317439ms) +✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.775366ms) +✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.184904ms) +✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.002224ms) +✔ F7: a symlinked file and a symlinked directory component are refused, never opened (8.106285ms) +✔ F8: a file swapped for a symlink after the catalogue is refused (2.045143ms) +✔ F9: registrations never add or redirect a root (1.483833ms) +✔ F10: a header cwd naming another project is refused (5.213162ms) +✔ F11: parentSession renders with a marker and the parent is never opened (1.019762ms) +✔ F12: two leaves: the default leaf is shown and the other branch reads alone (5.584279ms) +✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (3.432934ms) +✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.517508ms) +✔ F13: compaction is a marker in place, then the retained content (0.908764ms) +✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (598.605287ms) +✔ fragments never cut a surrogate pair and keep an empty string (4.606857ms) +✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.428557ms) +✔ unknown conversations, empty files and non-Pi files refuse (2.335997ms) +✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.050082ms) +✔ a seat directory without search permission refuses that root, not the catalogue (1.859272ms) +✔ every page and cursor is a valid CHAT-01 record (875.578923ms) +✔ the engine pin holds for the installed package (2.598793ms) +✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (324.810483ms) +✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (296.978768ms) +✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (74.83861ms) +✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (54.1458ms) +✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (22.98321ms) +✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.268534ms) +✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (36.45886ms) +✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (21.68886ms) +✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (116.161204ms) +✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (50.613061ms) +✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1533.053947ms) +✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (13.930099ms) +✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (70.710695ms) +✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (13.637596ms) +✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (14.878805ms) +✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (31.802435ms) +✔ N10: fake conformance (33.058444ms) +✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (29.244906ms) +✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (20.118833ms) +✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (64.60306ms) +✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (29.196526ms) +✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (26.842034ms) +✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (22.453252ms) +✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (12.680362ms) +✔ N17: the run fails on its own during the exchange: failed, Failed on its own (28.392304ms) +✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (105.211014ms) +✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (137.549503ms) +✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (88.798093ms) +✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (17.677504ms) +✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (13.362159ms) +✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (14.431043ms) +✔ 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.309547ms) +ℹ tests 152 +ℹ suites 0 +ℹ pass 152 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 30796.131987 diff --git a/agents/rocko/work/s4-follow-up/out/node-discord-r2.txt b/agents/rocko/work/s4-follow-up/out/node-discord-r2.txt new file mode 100644 index 00000000..c1873ec5 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-discord-r2.txt @@ -0,0 +1,186 @@ +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.903913ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.454462ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.78376ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.466664ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.513829ms) +✔ 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.416436ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.819423ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.289907ms) +✔ authorize: open channel, listed user (2.310838ms) +✔ authorize: wrong guild (0.217528ms) +✔ authorize: no guild (DM) (0.836277ms) +✔ authorize: unlisted channel (0.187908ms) +✔ authorize: unknown channel, no info (0.193527ms) +✔ authorize: thread of listed parent (0.188433ms) +✔ authorize: thread of unlisted parent (0.179334ms) +✔ authorize: text channel that is not a thread and not listed (0.144764ms) +✔ authorize: unlisted user (0.911939ms) +✔ authorize: no author (0.534731ms) +✔ authorize: bot author (listed id, bot flag) (0.146689ms) +✔ authorize: system author (0.115937ms) +✔ authorize: the bot itself (0.11845ms) +✔ authorize: webhook (0.092028ms) +✔ authorize: mention channel without mention (0.125109ms) +✔ authorize: mention channel with bot mention (0.127117ms) +✔ authorize: mention channel with @everyone only (0.101821ms) +✔ authorize: mention channel mentioning someone else (0.163548ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.109588ms) +✔ authorize: private thread under mention channel, mentioned (0.088943ms) +✔ authorize: private thread under mention channel, not mentioned (0.068659ms) +✔ authorize: thread in another guild per channel info (0.073402ms) +✔ authorize: not an object (0.058343ms) +✔ authorize: no id (0.061006ms) +✔ authorize: oversize content is accepted and flagged (0.068543ms) +✔ authorize: exactly the limit is not oversize (0.064352ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.438614ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.142735ms) +✔ binding: a complete binding validates and is frozen (2.739096ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.234553ms) +✔ binding: empty allowlists refuse (0.404158ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.2463ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.966692ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.087639ms) +✔ binding: file must be 0600, regular, not a symlink (1.8907ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.257554ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (85.300078ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.469484ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (302.831421ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (201.036945ms) +✔ cli: run refuses when STOP is present, before any network use (143.365084ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.880658ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.418763ms) +✔ 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.920871ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (12.913425ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.477812ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.129505ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.251699ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.479765ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.370296ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.673987ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.049364ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.633413ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (40.210132ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.715359ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.397667ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.75225ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.232436ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (3.891573ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.084563ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.457993ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (1.039986ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.343573ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.663203ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.06878ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.810866ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.269622ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.370235ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.793196ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.19291ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.351993ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.123168ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.634407ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.429101ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.391398ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (53.446831ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (37.021802ms) +✔ engine: one prompt, one turn, text and usage come back (29.426679ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (334.474382ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (234.697449ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.829241ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.393176ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (127.923407ms) +✔ 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.349312ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.508555ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.459239ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.530617ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (430.037768ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.965826ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (43.019075ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.435203ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.590545ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.577269ms) +✔ gateway: op 9 resumable resumes (0.365212ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.924753ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.575025ms) +✔ gateway: close() is final and unparseable frames are ignored (0.554273ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (68.40222ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (35.509171ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (63.337673ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.268017ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (70.998308ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (79.601099ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (88.922832ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (192.561515ms) +✔ git: push pushes the named branch only and reports up to date (70.937641ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (109.73177ms) +✔ git: the credential helper answers get over https from a private file and nothing else (188.202912ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (713.795919ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.865057ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (69.910142ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.703285ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.57031ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (34.852011ms) +✔ 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 (278.3905ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.422817ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.179621ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.995738ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (36.881048ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (143.185142ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (85.098925ms) +✔ notices: a kind is recorded per UTC day and found again (0.507347ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.001579ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.379525ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.684432ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (40.714778ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (32.096076ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (30.979192ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (68.06152ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (660.288689ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.514157ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.949474ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.716796ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.867047ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.117036ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.069278ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.897525ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.593861ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.103284ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (19.607646ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (7.030442ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.646527ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (2.898618ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.530955ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (1.757769ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1010.059937ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.418211ms) +✔ 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.24409ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.188286ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.190836ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.232401ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (2.757129ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (2.584477ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (3.375799ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.125719ms) +✔ tools: listing and search caps hold (7.523794ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.589112ms) +✔ 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.598801ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (0.817416ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.84635ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (3.75086ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (1.844748ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.243029ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.615671ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.494227ms) +✔ 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.780474ms) +✔ 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.288246ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.212091ms) +✔ 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.584111ms) +✔ 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.198462ms) +ℹ tests 178 +ℹ suites 0 +ℹ pass 178 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2592.580644 diff --git a/agents/rocko/work/s4-follow-up/out/node-ledger-r2.txt b/agents/rocko/work/s4-follow-up/out/node-ledger-r2.txt new file mode 100644 index 00000000..ff1d4d40 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-ledger-r2.txt @@ -0,0 +1,86 @@ +✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (118.594239ms) +✔ the raw path accepts nothing else, and refuses before curl runs (367.73863ms) +✔ the file checks still apply on the raw path: mode, symlink, missing, directory (277.674018ms) +✔ the raw path base URL has no override (52.543854ms) +✔ the JSON path is unchanged, and JSON never falls through to the raw path (244.478185ms) +✔ a file that changes between the two reads refuses before curl runs, with or without a body (711.198471ms) +✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (672.287612ms) +✔ real helper GET HTTP 200 preserves exit 0 without credentials (21.392231ms) +✔ real helper POST HTTP 201 preserves exit 0 without credentials (10.999711ms) +✔ real helper GET HTTP 403 preserves exit 1 without credentials (8.244951ms) +✔ fixture git subjects only, follow-ups and three session kinds (105.850533ms) +✔ text and JSON carry same numbers, open and truncated title (169.021156ms) +✔ missing credentials exit 2, no-issues never calls API and shows unknown (134.774246ms) +✔ empty range gives no rows and zero totals (96.267627ms) +✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (101.481753ms) +✔ close-only issue included, even median, missing metadata stays unknown (93.922638ms) +✔ unique commits but per-issue links count multiple tags once each (101.841089ms) +✔ page cap refuses rather than silently undercounting (87.159091ms) +✔ bad API payload not JSON refuses (89.245891ms) +✔ bad API payload {} refuses (87.247327ms) +✔ bad API payload [{"number":1}] refuses (82.516885ms) +✔ partial or malformed session log refuses with location, not content (94.357432ms) +✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (99.882598ms) +✔ no sessions is an empty table; symlink source refuses (163.470236ms) +✔ reads only refactor even when another branch is checked out (98.041163ms) +✔ invalid dates, reverse dates and duplicate options refuse (58.6285ms) +✔ T3 agent assignments do not count as human in Table 2 (95.217595ms) +✔ preamble parsing and issue number boundaries (0.46154ms) +✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.141378ms) +✔ no closed issues with human messages means undefined ratio, not invented zero (0.197281ms) +✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (185.160035ms) +✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (399.779566ms) +✔ T3: a HOME with no database exits 1 and names --no-t3 (90.585884ms) +✔ T3: a file that is not a database exits 1 and names --no-t3 (95.28352ms) +✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (113.166308ms) +✔ T3: an unmapped thread addressed as a seat exits 1 (111.036626ms) +✔ T3: a header to another thread id is not cross-checked (114.716086ms) +✔ T3: no project, or two, for this root exits 1 (290.567987ms) +✔ T3: a removed column exits 1 and names it (101.565435ms) +✔ T3: a missing table exits 1 and names it (99.926989ms) +✔ T3: a counted row with an unknown role exits 1 without its text (111.131468ms) +✔ T3: a counted row with non-text content exits 1 without its text (116.180417ms) +✔ T3: a counted row with an unparseable created_at exits 1 without its text (110.968807ms) +✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (197.389671ms) +✔ T3: a human message with no event counts in humanWithoutEvent (113.950005ms) +✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (204.679915ms) +✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (213.732484ms) +✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (59.38188ms) +✔ T3: a symlink at ~/.t3 exits 1 (83.748007ms) +✔ T3: a symlink at ~/.t3/userdata exits 1 (87.789686ms) +✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (83.319008ms) +✔ T3: with --t3-db, a symlinked file or directory exits 1 (170.291468ms) +✔ T3 WAL: the newest message only in -wal, writer attached, is counted (118.18355ms) +✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (119.273698ms) +✔ T3 WAL: -wal without -shm in a writable directory is read (138.87176ms) +✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (132.838681ms) +✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (118.205917ms) +✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5120.326126ms) +✔ a done row whose closing issue is open is a violation; a row that is not done is not (2.500601ms) +✔ an issue several rows close is expected closed only once all of them are done (0.73534ms) +✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.53954ms) +✔ each owner of an in-progress or in-review row gets one liveness class (13.651359ms) +✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.587481ms) +✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.315708ms) +✔ the text section always ends in a count and a result, and never prints a full pass (0.480685ms) +✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (23.386605ms) +✔ issue states: open list first, then the metric page, then at most 10 lookups (284.666989ms) +✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (365.749797ms) +✔ the open list refuses on a failed call or a bad record, and never echoes the helper (169.211568ms) +✔ a helper call past the deadline is killed with its child, and the call reports it (2007.38853ms) +✔ readQueue loads queue.json through the queue validator and refuses anything else (91.484931ms) +✔ protected changes list every in-range entry that changes a required or parked row (233.342814ms) +✔ the CLI prints the queue section above the weekly table and under a queue key in --json (486.462843ms) +✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (199.386399ms) +✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (258.105562ms) +✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (234.925552ms) +✔ --unsupported-runtime repeats once per seat and takes a seat name (301.420891ms) +✔ an unreadable config makes every owner invalid instead of passing them (124.051793ms) +ℹ tests 78 +ℹ suites 0 +ℹ pass 78 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 10715.167538 diff --git a/agents/rocko/work/s4-follow-up/out/node-mosaic-r2.txt b/agents/rocko/work/s4-follow-up/out/node-mosaic-r2.txt new file mode 100644 index 00000000..b528f465 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-mosaic-r2.txt @@ -0,0 +1,77 @@ +✔ pure resolution selects current default or explicit enrolled account (1.919587ms) +✔ scope is explicit, bounded and never inferred (1.579855ms) +✔ fork pin is preserved against default change, override, missing account and revocation (0.710634ms) +✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.798155ms) +✔ only explicit synthetic credential forms and internal fixture stores admitted (5.446372ms) +✔ two concurrent workspaces of the same agent publish distinct complete private generations (58.573612ms) +✔ same execution ID is exclusively claimed and cannot overwrite a published generation (36.350298ms) +✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (45.854624ms) +✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (55.323414ms) +✔ credential lock contention refuses without duplicate side effects (11.347744ms) +✔ symlinked pre-existing final target is refused and never followed (31.749281ms) +✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.398185ms) +✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (26.197757ms) +✔ expired credentials refresh under transaction and subsequent generation reuses rotation (85.726793ms) +✔ refresh failure retains prior generation and store state (67.795926ms) +✔ refresh timeout retains prior generation and store state (147.743057ms) +✔ refresh malformed retains prior generation and store state (65.246806ms) +✔ concurrent refresh on same account refuses contention while unrelated account proceeds (222.470507ms) +✔ invalid refresh options refuse before burning claim (38.452168ms) +✔ fixed fake process rotates both OAuth fields without mutating caller input (35.609313ms) +✔ concurrent isolated processes preserve separate provider credentials (26.763497ms) +✔ fake failure is refused with fixed diagnostics (28.24135ms) +✔ fake malformed is refused with fixed diagnostics (23.593098ms) +✔ fake timeout is refused with fixed diagnostics (103.490693ms) +✔ fake unchanged is refused with fixed diagnostics (25.131315ms) +✔ caller executable/environment injection is rejected before spawning (0.300073ms) +✔ valid fixture tree validates and lists without secrets (68.081332ms) +✔ unknown-field refuses (0.371759ms) +✔ invalid-id refuses uppercase and traversal shapes (0.268678ms) +✔ plain-http baseUrl requires allowInsecureTransport (0.296086ms) +✔ native provider rejects allowInsecureTransport (0.140857ms) +✔ unsupported credential type and kind refuse (0.147129ms) +✔ account provider-path mismatch refuses (0.10235ms) +✔ profile account refs must be provider/account shaped (0.625078ms) +✔ seat selection accepts fork pin field, validates account refs (0.217339ms) +✔ harness manifest id must equal executable (gate 1) (0.19595ms) +✔ CLI validate: duplicate provider id across files refuses (28.672577ms) +✔ CLI validate: missing referenced provider/account refuse (32.402305ms) +✔ CLI validate: broken JSON refuses without secret echo (28.101555ms) +✔ CLI usage errors exit 2 (55.542875ms) +✔ credential.json sibling presence does not break validation and is never read (65.193222ms) +✔ D1 missing, empty and structurally empty roots refuse, no list projection (168.473897ms) +✔ D1 required directory auth cannot be absent (53.820205ms) +✔ D1 required directory auth/providers cannot be absent (55.518075ms) +✔ D1 required directory auth/accounts cannot be absent (62.62363ms) +✔ D1 required directory auth/settings cannot be absent (68.901345ms) +✔ D1 required directory harnesses cannot be absent (62.781661ms) +✔ D1 root file and unreadable metadata refuse (113.227159ms) +✔ D2 no symlink traversal at auth/providers/openai-codex.json (56.362661ms) +✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (61.353629ms) +✔ D2 no symlink traversal at auth/providers (53.495206ms) +✔ D2 no symlink traversal at auth (53.821032ms) +✔ D2 root and ancestor symlinks and lexical traversal refuse (162.253987ms) +✔ private filesystem modes enforced for root (52.511966ms) +✔ private filesystem modes enforced for auth (56.016871ms) +✔ private filesystem modes enforced for auth/providers/openai-codex.json (55.381156ms) +✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (62.616506ms) +✔ D3 numeric version 1 only across all record kinds (1.233148ms) +✔ D4 nested unknown keys and missing per-kind required fields refuse (60.640569ms) +✔ D5 unenrolled default refuses even when account exists (64.638105ms) +✔ D6 provider/account credential type must match (62.814507ms) +✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.437418ms) +✔ D8 URLs reject embedded credentials and unsupported protocols without echo (167.695284ms) +✔ D9 malformed JSON diagnostics contain no content excerpt (61.482042ms) +✔ D10 missing metadata is missing-path, not invalid-json (62.199152ms) +✔ D10 library returns no partial entries on any invalid record (74.715426ms) +✔ null/scalar/array metadata refuses without stack or echo (249.850278ms) +✔ credential sibling is never opened, even when an unreadable symlink (32.363117ms) +✔ oversized metadata refuses before parsing (54.461836ms) +ℹ tests 69 +ℹ suites 0 +ℹ pass 69 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 2143.653413 diff --git a/agents/rocko/work/s4-follow-up/out/node-queue-r2.txt b/agents/rocko/work/s4-follow-up/out/node-queue-r2.txt new file mode 100644 index 00000000..7ac1c0cc --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-queue-r2.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 (1123.09366ms) +✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (906.626911ms) +ℹ 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 (878.906969ms) +ℹ 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 (961.495951ms) +✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (958.097988ms) +✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (898.953729ms) +✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1186.759984ms) +✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (793.011208ms) +✔ F1: a shared-index change during the procedure is not committed (912.048115ms) +✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (893.352413ms) +✔ F1: a missing or a different hook refuses (623.462627ms) +✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1085.782739ms) +✔ F1: the canary refuses a hook that git would not run (528.831803ms) +✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (806.638181ms) +✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (773.656262ms) +✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (813.111045ms) +✔ bootstrap: the archived tests and validator run outside any repository (773.607392ms) +✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (752.831693ms) +✔ general: a queue write after the snapshot is not committed (1090.50174ms) +✔ general: a snapshot whose log does not extend the base refuses (812.53443ms) +✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (162.956804ms) +✔ general: environment overrides, a linked worktree and usage (504.473454ms) +✔ general: a queue path staged before the run refuses at step 1 (554.652009ms) +✔ general: HEAD's queue tests failing in the archive refuse (745.828019ms) +✔ genesis document serializes deterministically and replays (4.087765ms) +✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.733583ms) +✔ a tampered result, receipt or viewSha fails replay (2.516496ms) +✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.199785ms) +✔ add: defaults for an ordinary seat, privileged extras, refusals (3.594784ms) +✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (4.995694ms) +✔ matrix: release, review round, changes requested and waiting-on-jason (11.698919ms) +✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (7.40716ms) +✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (18.043572ms) +✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.375591ms) +✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1327.424727ms) +✔ matrix: blocked keeps the claim and returns only to previousState (4.179038ms) +✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.796213ms) +✔ field edits: who may change what (5.608282ms) +✔ set issues keeps a logged narrowing of closes (N10) (2.806362ms) +✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.925822ms) +✔ every accepted text renders to nine cells on every row (N8) (22.358458ms) +✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.497087ms) +✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.624743ms) +✔ replay holds every op id to the caller's rule (N11) (4.934225ms) +✔ note: owner, listed reviewer or privileged; empty clears (1.171347ms) +✔ assign moves the claim with the owner; done clears it (2.628432ms) +✔ render is byte-stable and escapes pipes (0.766776ms) +✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.621348ms) +✔ next: resume, then review, then start, then wait, then nothing; lowest id first (18.560244ms) +✔ canonical args make a retry's identity independent of list order (0.303241ms) +✔ manifests, headings and blob ids (0.414845ms) +✔ the migration map: one queue-map block, exact keys (0.702117ms) +✔ every call but `queue` reaches the seat CLI exactly as before A2 (604.612956ms) +✔ `queue` reaches the queue CLI with the rest of the arguments (194.810538ms) +✔ the pre-A2 fixture is the script A2 changed (0.301444ms) +✔ acquire publishes the record by link; release removes only its own lock (4.229467ms) +✔ a kill between the temp write and the link leaves no lock (36.935103ms) +✔ a short or failed temp write refuses and leaves no lock and no temp (1.871304ms) +✔ a link error other than EEXIST refuses (1.181228ms) +✔ an error after the link releases the lock: unreadable gate, failing temp stat (2.907727ms) +✔ a release that fails on a gate path is reported, never a stack trace (P1) (4.290814ms) +✔ a paused holder: another writer waits 10 s, then refuses naming it live (10059.982969ms) +✔ two concurrent unlockers: the second refuses on the gate (22.476194ms) +✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (1.927059ms) +✔ a writer publishing during an unlock, gate first: the writer releases and refuses (1.722825ms) +✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (1.977909ms) +✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (3.383035ms) +✔ the same pid and start on a different boot is mismatch (0.741045ms) +✔ a foreign host is unknown whatever the local pid says; unlock refuses (46.957194ms) +✔ unreadable /proc: classification is unknown and acquire refuses (0.625757ms) +✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.23317ms) +✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (3.57997ms) +✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (2.147497ms) +✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (1.012782ms) +✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (3.955126ms) +✔ the migration map validates and renders the golden genesis table (3.302219ms) +✔ the marked QUEUE.md holds every row and parked item between its markers (1.156618ms) +✔ map-check reports each kind of drift (4.654279ms) +✔ a request posts once as the requester; a retry sends nothing (589.349761ms) +✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (2965.786909ms) +✔ the pre-send checks: GET user must name the requester, under the deadline (942.495573ms) +✔ the lead's request refuses a token for login sage (502.310836ms) +✔ the credential file: the seat's own, 0600, no symlink, never the shared default (429.46681ms) +✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (1329.366392ms) +✔ a same-op retry after a kill sends nothing, even with a stale view (2033.657318ms) +✔ a held lock at the outcome exits 3 and names what the transport said (534.566132ms) +✔ late outcomes: after an abandon, and after a resolve with the same or another id (1367.135232ms) +✔ resolve checks the comment: issue, markers, round, candidate and author (1390.301985ms) +✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (462.37746ms) +✔ validateRow checks a request round's shape, which every replayed entry must keep (310.297933ms) +✔ request, changes, a new candidate, approval: every round pinned; no review files (1268.058829ms) +✔ a row with no reviewers opens a round that posts nothing (748.981551ms) +✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1054.353712ms) +✔ semantics: v1 entries replay as before; review entries need v2 (309.071342ms) +✔ set reviewers refuses the row's owner (2026-09-28) (240.118293ms) +✔ the owner records no verdict, even as a listed reviewer (311.114905ms) +✔ a request comment over the length limit is not sent (351.569715ms) +✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (356.668232ms) +✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (1642.075608ms) +✔ genesis: refusals before anything is written (440.209638ms) +✔ genesis: the map must be committed, well formed, with committed briefs and seats (552.297366ms) +✔ genesis: markers, a stray witness, once only; a retry returns the receipt (463.823172ms) +✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (279.997243ms) +✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (570.928229ms) +✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (480.71357ms) +✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (567.401069ms) +✔ a retried add returns the id it first allocated, after reassignment and after done (589.036439ms) +✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (419.946608ms) +✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (783.814932ms) +✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (575.769966ms) +✔ add, set reviewers and assign refuse the row's owner as a reviewer (330.238827ms) +✔ the working-brief check: a changed working copy refuses the start and flags next (512.003779ms) +✔ next: resume first, then nothing for an idle seat; needs a seat (264.440746ms) +✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (508.26713ms) +✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (928.114596ms) +✔ a hand edit to queue.json refuses every verb, reads included (482.195771ms) +✔ verify and render --check leave bytes and mtimes unchanged (358.437948ms) +✔ render is byte-stable across runs and repositories (209.006408ms) +✔ snapshot and verify --snapshot (616.971903ms) +✔ usage errors exit 4 (517.000537ms) +✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (161.525767ms) +✔ a rename failure: nothing visible, temp removed (130.615185ms) +✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (153.433215ms) +✔ a directory fsync failure, then sync names the op (271.248573ms) +✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (138.332545ms) +✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (122.960464ms) +✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (131.07291ms) +✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (109.887142ms) +✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (120.207877ms) +✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (130.250122ms) +✔ unlock prints a swapped gate's warning on stderr, the result on stdout (104.778697ms) +✔ a view write that fails keeps the op and reports a stale view (105.263596ms) +✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (544.001901ms) +✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (610.622764ms) +✔ SIGKILL after the witness, before the view: the stale refusal names the op (569.060997ms) +✔ SIGKILL after the view, before the receipt: the retry returns the receipt (574.825452ms) +✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (132.228675ms) +✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (782.093087ms) +✔ a deleted witness: refused after the locked recheck; accept-history records it absent (169.363828ms) +✔ a header edit during a write: the op stands, the view write is skipped with a warning (108.565309ms) +✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (117.868726ms) +✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (120.002769ms) +✔ a writer paused before and after the witness rename: readers see a tail, then a match (117.337284ms) +✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (472.231055ms) +✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (433.987458ms) +✔ the platform check refuses other filesystems (97.893225ms) +✔ tmpfs passes only a test layer that allows it (N5) (126.487748ms) +✔ unlock keeps a multi-line lock record on stdout (P3) (149.811729ms) +ℹ tests 148 +ℹ suites 0 +ℹ pass 148 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 19601.279263 diff --git a/agents/rocko/work/s4-follow-up/out/node-seat-r2.txt b/agents/rocko/work/s4-follow-up/out/node-seat-r2.txt new file mode 100644 index 00000000..eb65dc45 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-seat-r2.txt @@ -0,0 +1,27 @@ +✔ resolveSeat: by name under --repo resolves the repo layout (1.201331ms) +✔ resolveSeat: by path resolves the fleet layout (0.336482ms) +✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.70358ms) +✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.63805ms) +✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.787247ms) +✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.280028ms) +✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.097861ms) +✔ CLI launch: registers, execs the fake launch script, and passes args through (29.046665ms) +✔ CLI launch: --harness lands in the record (28.749652ms) +✔ CLI launch: the launch script's own exit code passes through (27.528268ms) +✔ CLI launch: relaunching a seat rewrites the one registration record (58.113927ms) +✔ CLI launch: omitting --task records an empty string, not null (30.100879ms) +✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (78.310326ms) +✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (135.586594ms) +✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.371918ms) +✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.564905ms) +✔ 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.694039ms) +✔ 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 (0.860769ms) +✔ 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 (264.773945ms) +ℹ tests 19 +ℹ suites 0 +ℹ pass 19 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 711.455044 diff --git a/agents/rocko/work/s4-follow-up/out/node-tasks-r2.txt b/agents/rocko/work/s4-follow-up/out/node-tasks-r2.txt new file mode 100644 index 00000000..57b8767b --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-tasks-r2.txt @@ -0,0 +1,59 @@ +✔ the boot config is checked before anything starts (13.525058ms) +✔ a business with no tracker entry refuses task verbs (11.657263ms) +✔ credential.expiring and .expired are recorded once per instance (28.491107ms) +✔ a token file that changes on disk records credential.changed (12.173554ms) +✔ autostart polls, reconciles and retries a startup the tracker was down for (35.116846ms) +✔ a refusal a restart must clear is not retried by the poll (7.934045ms) +✔ a poll that fires while two are queued is dropped (12.221434ms) +✔ close waits for a running verb and refuses one that has not started (40.109683ms) +✔ the bundled Vikunja is the pinned upstream image the runbook names (0.854356ms) +✔ every published port is on 127.0.0.1, and no secret is in the file (0.320283ms) +✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (89.076359ms) +✔ the recorded task bodies pass the checks S3 applies to every read (0.496214ms) +✔ the client works against the fake over real HTTP with the platform fetch (93.784381ms) +✔ a correct install starts, and the first reconcile records tasks that already exist (28.196029ms) +✔ verbs refuse while a business is starting and after startup refused it (15.096596ms) +✔ startup refuses a token that can do more than its role needs (35.919401ms) +✔ startup refuses an unsupported version and flags an untested one (25.521099ms) +✔ startup refuses a board that the runbook did not install (33.062122ms) +✔ startup refuses a project the sync bot cannot read (6.775512ms) +✔ startup refuses a configured label the pm bot cannot see (7.808352ms) +✔ startup refuses an expired credential and a missing sync credential (15.64388ms) +✔ an unreachable tracker refuses with tracker-unavailable (6.503175ms) +✔ an edit in the UI is recorded once, with the fields that changed (79.122007ms) +✔ a move between open buckets is seen on the board, though updated does not change (37.816485ms) +✔ a person's comment is counted and a bot's is not (65.42748ms) +✔ the hourly reconcile catches a comment through comment_count (75.271868ms) +✔ a task closed in the UI leaves the open view with its done bucket (108.166875ms) +✔ a task that leaves the board is recorded as deleted, moved or out of reach (60.936271ms) +✔ a poll that read before a verb wrote does not overwrite the verb (32.571499ms) +✔ a tracker fault during a tick is reported and the next tick catches up (34.564496ms) +✔ a malformed answer refuses the tick with tracker-shape (11.06167ms) +✔ no token value reaches the database, the log or a refusal (42.85857ms) +✔ the first look at a task counts only comments inside the window (35.713216ms) +✔ task.create needs a recorded human request and a requirement id (37.987302ms) +✔ only labels named in the business file can be written (25.590207ms) +✔ task.schedule sets and clears a due date and relations (30.728864ms) +✔ assign and reassign move the role bots and record task.assigned (51.194862ms) +✔ task.update.assigned is for the assignee and records task.state (49.737162ms) +✔ a wrong expected digest records task.conflict and writes nothing (16.178182ms) +✔ a cross-role verb needs a resolved decision, used once (19.604384ms) +✔ task.close needs a verdict; after it every verb refuses with task-done (19.639506ms) +✔ a lost answer is settled by a re-read and never retried (22.484145ms) +✔ a create whose answer is lost is reported uncertain, and the poll finds the task (34.524476ms) +✔ a task the sync bot cannot read refuses and records nothing (13.739975ms) +✔ verbs and polls for one business run one at a time (83.390667ms) +✔ a due date with milliseconds is written to the second (60.693013ms) +✔ every write landed and the final read failed: the verb succeeds and records what it wrote (36.698399ms) +✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (13.750686ms) +✔ a create whose final read fails succeeds and records task.created (33.557305ms) +✔ an edit between the last write and the final read shows as external on the next poll (35.962535ms) +✔ task.created is recorded when a later label write fails (11.383503ms) +ℹ tests 51 +ℹ suites 0 +ℹ pass 51 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 683.275814 diff --git a/agents/rocko/work/s4-follow-up/out/node-webui-r2.txt b/agents/rocko/work/s4-follow-up/out/node-webui-r2.txt new file mode 100644 index 00000000..2af598f6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/node-webui-r2.txt @@ -0,0 +1,23 @@ +✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (2176.618252ms) +Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} +✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (2178.390524ms) +✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (1980.754608ms) +✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1284.842187ms) +✔ conversation view: a newer session with no readable history keeps the marker (854.620038ms) +✔ conversation view: seats without history say so and offer no reply (559.16708ms) +✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (1658.06713ms) +✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (52597.312061ms) +✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (1714.825956ms) +✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21573.357273ms) +✔ loopback host and board origin fail closed (6.341086ms) +✔ real board fixture passes through WebUI; assets and isolated seen/reply work (76.074161ms) +✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (47.426114ms) +✔ unreachable board reports URL; CLI rejects unsupported options (411.36665ms) +ℹ tests 14 +ℹ suites 0 +ℹ pass 14 +ℹ fail 0 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 52851.310746 diff --git a/agents/rocko/work/s4-follow-up/out/r2/manifest-check-mut.txt b/agents/rocko/work/s4-follow-up/out/r2/manifest-check-mut.txt new file mode 100644 index 00000000..25064faa --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/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/r2/mut-D3.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-D3.txt new file mode 100644 index 00000000..4c77fcf6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-D3.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (29.760605ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.387701ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (19.630873ms) +✔ decide prints a declining choice as declining (18.559833ms) +✔ an unknown outcome is reported once and never resent (19.251059ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.774078ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.392506ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (19.895737ms) +✔ every human command refuses inside an agent run before it touches the bus (21.584274ms) +✔ usage errors exit 4; no business and no host is a usage error (17.797736ms) +✔ agents and tasks print through the broker (16.859557ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.04971ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (74.7938ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.571683ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (60.48769ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (57.400902ms) +✔ a business without tracker.baseUrl gets no trackers entry (60.437397ms) +✔ an unknown business and a broken system config refuse with exit 3 (85.017421ms) +✔ empty views say so (1.072336ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.521006ms) +✔ tasks print the tracker fields the snapshot carries (0.24846ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (977.003584ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (140.658962ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.178326ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (133.82799ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (125.348276ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (98.619915ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (129.898413ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.795733ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.043961ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (27.113002ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.695286ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (208.18818ms) +✔ bus start refuses with exit 3 without a notifier config (88.154146ms) +✔ bus start runs until bus stop; status reports it while it runs (602.606373ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.695514ms) +✔ zoned uses the IANA zone across DST (28.539915ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.090678ms) +✔ two blocking decisions get two DMs with different nonces (27.568839ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.336713ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (20.089529ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (25.438663ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (26.87194ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (21.403229ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (143.282607ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (65.831031ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.145753ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (13.046842ms) +✔ an inbox read failure is logged and the next poll retries (0.793326ms) +✔ no Discord id reaches the journal or the log (12.404002ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.007638ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.188473ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.627255ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.496929ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.561601ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.770868ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.339127ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.511845ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.423929ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.491457ms) +✔ digest content stays within Discord's 2000 characters (0.29813ms) +✔ runLoop never overlaps ticks and stops after the one in flight (112.067241ms) +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 (369.773535ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (58.915322ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2285.30706ms) +✔ busExit and refuseInsideAgent (0.408639ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.188646ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.756135ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.597795ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.624234ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.999595ms) +✔ 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.349429ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.400713ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.732256ms) +✔ authorize: open channel, listed user (2.101826ms) +✔ authorize: wrong guild (0.209367ms) +✔ authorize: no guild (DM) (0.162187ms) +✔ authorize: unlisted channel (0.179388ms) +✔ authorize: unknown channel, no info (0.382868ms) +✔ authorize: thread of listed parent (0.197633ms) +✔ authorize: thread of unlisted parent (0.160359ms) +✔ authorize: text channel that is not a thread and not listed (0.155157ms) +✔ authorize: unlisted user (0.991643ms) +✔ authorize: no author (0.295573ms) +✔ authorize: bot author (listed id, bot flag) (0.140998ms) +✔ authorize: system author (0.55616ms) +✔ authorize: the bot itself (0.099859ms) +✔ authorize: webhook (0.113831ms) +✔ authorize: mention channel without mention (0.136751ms) +✔ authorize: mention channel with bot mention (0.145852ms) +✔ authorize: mention channel with @everyone only (0.104218ms) +✔ authorize: mention channel mentioning someone else (0.10662ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.150781ms) +✔ authorize: private thread under mention channel, mentioned (0.094834ms) +✔ authorize: private thread under mention channel, not mentioned (0.077781ms) +✔ authorize: thread in another guild per channel info (0.081945ms) +✔ authorize: not an object (0.059755ms) +✔ authorize: no id (0.06135ms) +✔ authorize: oversize content is accepted and flagged (0.066517ms) +✔ authorize: exactly the limit is not oversize (0.066867ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.472156ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.154326ms) +✔ binding: a complete binding validates and is frozen (2.522631ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.552805ms) +✔ binding: empty allowlists refuse (0.474243ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.795377ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.668973ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.432969ms) +✔ binding: file must be 0600, regular, not a symlink (1.577308ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.402369ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (122.273784ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.82173ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (397.984547ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (198.528071ms) +✔ cli: run refuses when STOP is present, before any network use (139.989087ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.79887ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.297895ms) +✔ 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.070442ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.563682ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.483077ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.077325ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.172985ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.471663ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (31.650079ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.809468ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.128839ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.115303ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.156008ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.454802ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.007986ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.213715ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.508761ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.672546ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.949083ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.817555ms) +✔ 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.550261ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (5.493833ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (7.417453ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.881282ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.910144ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.472195ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.849994ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.776002ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.159095ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.417185ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.240973ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.659696ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.087237ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.685236ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (71.638604ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (45.725937ms) +✔ engine: one prompt, one turn, text and usage come back (56.573313ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (369.23746ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (234.018661ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.249644ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (226.457903ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (127.47982ms) +✔ 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.341342ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.421315ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.317539ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.505815ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.748202ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.530213ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (52.15548ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.592007ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.584807ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.499142ms) +✔ gateway: op 9 resumable resumes (0.368765ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.76534ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.741377ms) +✔ gateway: close() is final and unparseable frames are ignored (0.408938ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (73.103743ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (66.480583ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (130.774678ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.470491ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (97.943837ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (88.474131ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (95.746066ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (200.028001ms) +✔ git: push pushes the named branch only and reports up to date (71.359196ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (82.998207ms) +✔ git: the credential helper answers get over https from a private file and nothing else (194.931382ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (759.877143ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.051919ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (95.710236ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.103282ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.743815ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (70.748104ms) +✔ 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 (339.267476ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.478894ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.98535ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.986754ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (45.428384ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (140.01096ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (86.917507ms) +✔ notices: a kind is recorded per UTC day and found again (0.479905ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.712815ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.022034ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.770634ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (56.608994ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (57.629133ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (42.713779ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (84.057658ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (665.388638ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.224846ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.491371ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.855465ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.855279ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.965654ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.49081ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.944926ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.625227ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.165291ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.520967ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.387858ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.198956ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.556572ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.614136ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.8708ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.976724ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.479047ms) +✔ 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.557741ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.258712ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.206733ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.564429ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.434266ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.960139ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.559793ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.934678ms) +✔ tools: listing and search caps hold (11.939271ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.984883ms) +✔ 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.626118ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.136801ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.307826ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.524896ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.015756ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.701988ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.340098ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.825342ms) +✔ 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.850229ms) +✔ 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.215255ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.220765ms) +✔ 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.577992ms) +✔ 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.207962ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3201.814177 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:332:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.770868ms) + AssertionError [ERR_ASSERTION]: Missing expected exception: day: {"at":"2026-10-08T12:00:00.000Z","kind":"digest","decision":null,"day":"2026-13-45","outcome":"confirmed","messageId":"2"} + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:361: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/r2/mut-E1.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-E1.txt new file mode 100644 index 00000000..b14195cb --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-E1.txt @@ -0,0 +1,287 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (34.933054ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.586516ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (17.071468ms) +✔ decide prints a declining choice as declining (20.782453ms) +✔ an unknown outcome is reported once and never resent (15.525174ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.507269ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (16.66138ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (15.815819ms) +✔ every human command refuses inside an agent run before it touches the bus (13.792254ms) +✔ usage errors exit 4; no business and no host is a usage error (22.532964ms) +✔ agents and tasks print through the broker (16.142383ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.867672ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (93.712278ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (55.603432ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.026832ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (59.84363ms) +✔ a business without tracker.baseUrl gets no trackers entry (51.074233ms) +✔ an unknown business and a broken system config refuse with exit 3 (78.386348ms) +✔ empty views say so (1.271252ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.3584ms) +✔ tasks print the tracker fields the snapshot carries (0.215963ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (955.861046ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (147.043559ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (72.581752ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (127.282474ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (120.073724ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (74.485241ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (128.852161ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (74.254731ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (154.961828ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.825489ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.095679ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (199.499294ms) +✔ bus start refuses with exit 3 without a notifier config (84.245811ms) +✔ bus start runs until bus stop; status reports it while it runs (601.832061ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.056634ms) +✔ zoned uses the IANA zone across DST (25.127665ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (36.414145ms) +✔ two blocking decisions get two DMs with different nonces (18.357286ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.315889ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (16.223452ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (23.260855ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (26.808591ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (21.001178ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (149.53931ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (52.44175ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (13.60806ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.889479ms) +✔ an inbox read failure is logged and the next poll retries (0.84053ms) +✔ no Discord id reaches the journal or the log (11.681828ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.054497ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.500186ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.68153ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.521073ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.65964ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.43279ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.41309ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.554531ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.440515ms) +✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.878258ms) +✔ digest content stays within Discord's 2000 characters (0.290256ms) +✔ runLoop never overlaps ticks and stops after the one in flight (109.820304ms) +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 (357.256831ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (62.429113ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2263.493502ms) +✔ busExit and refuseInsideAgent (0.404764ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.21458ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.657051ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.627824ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.478332ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (21.332276ms) +✔ 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.963611ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.08662ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.496166ms) +✔ authorize: open channel, listed user (2.030134ms) +✔ authorize: wrong guild (0.193645ms) +✔ authorize: no guild (DM) (0.175263ms) +✔ authorize: unlisted channel (0.190754ms) +✔ authorize: unknown channel, no info (0.180484ms) +✔ authorize: thread of listed parent (0.415865ms) +✔ authorize: thread of unlisted parent (0.258485ms) +✔ authorize: text channel that is not a thread and not listed (0.147594ms) +✔ authorize: unlisted user (0.394631ms) +✔ authorize: no author (0.296242ms) +✔ authorize: bot author (listed id, bot flag) (0.1591ms) +✔ authorize: system author (0.143776ms) +✔ authorize: the bot itself (0.091701ms) +✔ authorize: webhook (0.113823ms) +✔ authorize: mention channel without mention (0.166028ms) +✔ authorize: mention channel with bot mention (2.248183ms) +✔ authorize: mention channel with @everyone only (0.258813ms) +✔ authorize: mention channel mentioning someone else (0.1227ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.096363ms) +✔ authorize: private thread under mention channel, mentioned (0.087809ms) +✔ authorize: private thread under mention channel, not mentioned (0.070144ms) +✔ authorize: thread in another guild per channel info (0.081757ms) +✔ authorize: not an object (0.066607ms) +✔ authorize: no id (0.057509ms) +✔ authorize: oversize content is accepted and flagged (0.064267ms) +✔ authorize: exactly the limit is not oversize (0.057648ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.485833ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.137947ms) +✔ binding: a complete binding validates and is frozen (2.609785ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.333335ms) +✔ binding: empty allowlists refuse (0.387055ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.5243ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.943568ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.214392ms) +✔ binding: file must be 0600, regular, not a symlink (2.328198ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.399884ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (115.048247ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.047758ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (384.18446ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (186.331009ms) +✔ cli: run refuses when STOP is present, before any network use (130.382041ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.777309ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.201288ms) +✔ 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 (27.547373ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.250452ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.607876ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.117282ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.197262ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.377091ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.636836ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.461827ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.585965ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.42583ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.695013ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.728311ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.742455ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.046873ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (1.741964ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.967723ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.054239ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.757555ms) +✔ 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.440423ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.246304ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.360643ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.12885ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.921058ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.720076ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.092018ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.005291ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.209847ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.579851ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (2.193647ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.732547ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.872288ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.46565ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (51.958902ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (50.280945ms) +✔ engine: one prompt, one turn, text and usage come back (43.633685ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (363.478196ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (239.226412ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.390613ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.487928ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (137.513919ms) +✔ 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.478157ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.2532ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.322802ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.5683ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.877002ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.269469ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.061713ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.405247ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.554267ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.51209ms) +✔ gateway: op 9 resumable resumes (0.33714ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.785509ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.502693ms) +✔ gateway: close() is final and unparseable frames are ignored (0.385894ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (74.382799ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (58.763881ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (121.730986ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.350252ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (95.164082ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (82.677506ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (83.789469ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (200.720392ms) +✔ git: push pushes the named branch only and reports up to date (73.770788ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (86.440968ms) +✔ git: the credential helper answers get over https from a private file and nothing else (201.00243ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (765.686154ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.665994ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (82.834681ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.045811ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.737672ms) +✔ 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.452895ms) +✔ 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 (305.142207ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.395347ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.560325ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.028335ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (47.715684ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (147.940445ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (93.267207ms) +✔ notices: a kind is recorded per UTC day and found again (0.485469ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.702771ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.033328ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.585238ms) +✔ 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.94715ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (51.181476ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (38.667192ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (74.673209ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (633.782043ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.715976ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.224635ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.593037ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.652803ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.88761ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.471402ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.888135ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.469303ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.845624ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.381107ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.91462ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.297829ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.778139ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.473772ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.461485ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.148874ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.430035ms) +✔ 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.570636ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.258537ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.209404ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.661587ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.225792ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.677205ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.675501ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.702477ms) +✔ tools: listing and search caps hold (10.94082ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.855241ms) +✔ 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.139971ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.249232ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.112909ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.751631ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.873977ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.880282ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.292493ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.842155ms) +✔ 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.430881ms) +✔ 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.252925ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.248327ms) +✔ 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.722402ms) +✔ 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.616528ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3127.553943 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:410:1 +✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.878258ms) + 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-5J6JN7/notify/demo/sent.jsonl + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:417: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-5J6JN7/notify/demo/sent.jsonl + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:185:44) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:417: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:417: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/r2/mut-E2.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-E2.txt new file mode 100644 index 00000000..2a3789d5 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-E2.txt @@ -0,0 +1,287 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (30.80592ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (37.941349ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (24.665665ms) +✔ decide prints a declining choice as declining (16.704628ms) +✔ an unknown outcome is reported once and never resent (18.362108ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (22.967575ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.921798ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (19.349994ms) +✔ every human command refuses inside an agent run before it touches the bus (23.929134ms) +✔ usage errors exit 4; no business and no host is a usage error (19.440884ms) +✔ agents and tasks print through the broker (18.568282ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.487931ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (69.7928ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (71.846687ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (57.044383ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.644273ms) +✔ a business without tracker.baseUrl gets no trackers entry (60.90405ms) +✔ an unknown business and a broken system config refuse with exit 3 (84.738424ms) +✔ empty views say so (0.990899ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.31507ms) +✔ tasks print the tracker fields the snapshot carries (0.201898ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (980.69948ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (141.208608ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (74.75781ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.932544ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (119.885869ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (84.033505ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (125.225389ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (79.318402ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (127.759584ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (20.647585ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.650482ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (211.881612ms) +✔ bus start refuses with exit 3 without a notifier config (86.249024ms) +✔ bus start runs until bus stop; status reports it while it runs (596.571005ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.992768ms) +✔ zoned uses the IANA zone across DST (26.355309ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.970798ms) +✔ two blocking decisions get two DMs with different nonces (20.501669ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.333487ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (20.656415ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (22.497303ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.097279ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (22.478863ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (152.705588ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (63.602167ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.846273ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.902115ms) +✔ an inbox read failure is logged and the next poll retries (0.696838ms) +✔ no Discord id reaches the journal or the log (10.193279ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.286875ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.83902ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.342191ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.548612ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.599609ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.983624ms) +✖ the journal: a symlinked directory refuses and says it is a link (0.811472ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.509637ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.39318ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.466583ms) +✔ digest content stays within Discord's 2000 characters (0.845689ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.857557ms) +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.993838ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (60.080609ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2271.743692ms) +✔ busExit and refuseInsideAgent (3.612956ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.25813ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.294402ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.549751ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.47292ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (14.969516ms) +✔ 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.513918ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.227274ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.701524ms) +✔ authorize: open channel, listed user (1.959092ms) +✔ authorize: wrong guild (0.216453ms) +✔ authorize: no guild (DM) (0.170078ms) +✔ authorize: unlisted channel (0.195683ms) +✔ authorize: unknown channel, no info (0.23938ms) +✔ authorize: thread of listed parent (0.185313ms) +✔ authorize: thread of unlisted parent (0.159428ms) +✔ authorize: text channel that is not a thread and not listed (0.150142ms) +✔ authorize: unlisted user (0.962821ms) +✔ authorize: no author (0.270791ms) +✔ authorize: bot author (listed id, bot flag) (0.148066ms) +✔ authorize: system author (0.140795ms) +✔ authorize: the bot itself (0.105239ms) +✔ authorize: webhook (0.093834ms) +✔ authorize: mention channel without mention (0.155979ms) +✔ authorize: mention channel with bot mention (0.156979ms) +✔ authorize: mention channel with @everyone only (0.209198ms) +✔ authorize: mention channel mentioning someone else (0.572704ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.093317ms) +✔ authorize: private thread under mention channel, mentioned (0.087315ms) +✔ authorize: private thread under mention channel, not mentioned (0.063573ms) +✔ authorize: thread in another guild per channel info (0.062634ms) +✔ authorize: not an object (0.059682ms) +✔ authorize: no id (0.059357ms) +✔ authorize: oversize content is accepted and flagged (0.789309ms) +✔ authorize: exactly the limit is not oversize (0.086833ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.472122ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.187836ms) +✔ binding: a complete binding validates and is frozen (2.594047ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.43093ms) +✔ binding: empty allowlists refuse (0.640449ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.346187ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.700946ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.223966ms) +✔ binding: file must be 0600, regular, not a symlink (1.536256ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.450629ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (110.20281ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.845798ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (437.195233ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (198.834255ms) +✔ cli: run refuses when STOP is present, before any network use (140.07413ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.065311ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.126689ms) +✔ 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.238463ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.816258ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.53663ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.12218ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.512308ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.88486ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.236004ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.001692ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.373969ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.243393ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.635205ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (35.994945ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.116644ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.278894ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.234631ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.540188ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.880503ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.732181ms) +✔ 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.767746ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (6.139385ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.41613ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.126854ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.543543ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.348612ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.798247ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.750549ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.15992ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.468416ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.285245ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.184191ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.58938ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.469604ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (57.493171ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (51.29494ms) +✔ engine: one prompt, one turn, text and usage come back (51.675881ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (363.088551ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (228.510724ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.701379ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.035177ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (135.507868ms) +✔ 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.449236ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.742805ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.235627ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.502668ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.168525ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.983317ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.06491ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.570315ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.813386ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.550125ms) +✔ gateway: op 9 resumable resumes (0.390354ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.833395ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.468572ms) +✔ gateway: close() is final and unparseable frames are ignored (0.411776ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.207396ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (65.182611ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (146.161346ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.419711ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (93.371319ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (114.644759ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (92.718272ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (215.787199ms) +✔ git: push pushes the named branch only and reports up to date (67.738301ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (81.54714ms) +✔ git: the credential helper answers get over https from a private file and nothing else (194.467721ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (735.065155ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.732554ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (100.769657ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.129857ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (7.58865ms) +✔ 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.822334ms) +✔ 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 (374.981472ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.597039ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (32.402789ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.201646ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (42.397274ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (137.466081ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (86.666807ms) +✔ notices: a kind is recorded per UTC day and found again (0.40965ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.665911ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.356813ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.838863ms) +✔ 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.890843ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (60.66429ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.115231ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (96.310687ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (670.803332ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.278146ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.892925ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.539036ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.440505ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.845807ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.476087ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.307226ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.887012ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.236875ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.831211ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.545179ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.060634ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.631006ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.676231ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.481228ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.264905ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.41958ms) +✔ 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.921007ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.26201ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.337149ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.32135ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.367336ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.324124ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.206261ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.556235ms) +✔ tools: listing and search caps hold (14.90763ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.871245ms) +✔ 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.010288ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.369828ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.393609ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.344622ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.153513ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.476181ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.200898ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.395614ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1032.626989ms) +✔ 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.188592ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.227019ms) +✔ 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.619079ms) +✔ 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.172735ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3138.692892 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:373:1 +✖ the journal: a symlinked directory refuses and says it is a link (0.811472ms) + 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-vNPN55/notify/demo + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:380: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-vNPN55/notify/demo + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:171:11) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:380: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:380: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/r2/mut-F2a.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-F2a.txt new file mode 100644 index 00000000..59a096cf --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-F2a.txt @@ -0,0 +1,296 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (34.457669ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (39.776212ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (27.600945ms) +✔ decide prints a declining choice as declining (15.940212ms) +✔ an unknown outcome is reported once and never resent (16.749822ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.791857ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (13.234687ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (19.37181ms) +✔ every human command refuses inside an agent run before it touches the bus (23.136445ms) +✔ usage errors exit 4; no business and no host is a usage error (20.99572ms) +✔ agents and tasks print through the broker (17.491638ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.941933ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (79.89992ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.021301ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (57.661152ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.772844ms) +✔ a business without tracker.baseUrl gets no trackers entry (62.196252ms) +✔ an unknown business and a broken system config refuse with exit 3 (99.376599ms) +✔ empty views say so (1.116849ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.287586ms) +✔ tasks print the tracker fields the snapshot carries (0.38398ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (970.94765ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (141.475956ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (70.074611ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.594938ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (122.05514ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (83.471111ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (130.578086ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (80.773985ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (129.291133ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.951038ms) +✔ bus stop refuses to signal a live pid that is not a bus host (201.98111ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (214.706423ms) +✔ bus start refuses with exit 3 without a notifier config (83.562569ms) +✔ bus start runs until bus stop; status reports it while it runs (591.731588ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.066894ms) +✔ zoned uses the IANA zone across DST (27.340622ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (36.416407ms) +✔ two blocking decisions get two DMs with different nonces (31.196857ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.317673ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (28.754351ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (1.583579ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (34.761097ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.059918ms) +✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (153.389297ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (71.565471ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.956347ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (14.297155ms) +✔ an inbox read failure is logged and the next poll retries (0.821125ms) +✔ no Discord id reaches the journal or the log (15.565565ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.563285ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.30832ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.759443ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.711908ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.691527ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.787096ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.413294ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.60859ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.473971ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.594826ms) +✔ digest content stays within Discord's 2000 characters (0.330817ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.534829ms) +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 (386.924727ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (59.064308ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2271.756605ms) +✔ busExit and refuseInsideAgent (0.430765ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.227106ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.540684ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.62685ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.479888ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.627827ms) +✔ 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.471183ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.524196ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.657375ms) +✔ authorize: open channel, listed user (2.064009ms) +✔ authorize: wrong guild (0.22122ms) +✔ authorize: no guild (DM) (0.181135ms) +✔ authorize: unlisted channel (0.22082ms) +✔ authorize: unknown channel, no info (0.170662ms) +✔ authorize: thread of listed parent (0.151932ms) +✔ authorize: thread of unlisted parent (0.154559ms) +✔ authorize: text channel that is not a thread and not listed (0.132508ms) +✔ authorize: unlisted user (0.22058ms) +✔ authorize: no author (1.165386ms) +✔ authorize: bot author (listed id, bot flag) (0.334929ms) +✔ authorize: system author (0.214155ms) +✔ authorize: the bot itself (0.12664ms) +✔ authorize: webhook (0.175548ms) +✔ authorize: mention channel without mention (0.181537ms) +✔ authorize: mention channel with bot mention (0.151418ms) +✔ authorize: mention channel with @everyone only (0.096097ms) +✔ authorize: mention channel mentioning someone else (0.079812ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.067777ms) +✔ authorize: private thread under mention channel, mentioned (0.089663ms) +✔ authorize: private thread under mention channel, not mentioned (0.076655ms) +✔ authorize: thread in another guild per channel info (0.105833ms) +✔ authorize: not an object (0.073392ms) +✔ authorize: no id (0.075666ms) +✔ authorize: oversize content is accepted and flagged (0.072669ms) +✔ authorize: exactly the limit is not oversize (0.074601ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.48884ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.200971ms) +✔ binding: a complete binding validates and is frozen (2.476085ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.226904ms) +✔ binding: empty allowlists refuse (0.368308ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.348988ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.751437ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.550265ms) +✔ binding: file must be 0600, regular, not a symlink (2.38952ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.983162ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (110.66854ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.066087ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (432.827415ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (206.863956ms) +✔ cli: run refuses when STOP is present, before any network use (136.110665ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.686294ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.092118ms) +✔ 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.053257ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.088716ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.630096ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.245316ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.333303ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.411725ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.777624ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.04499ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.084472ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.654419ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.170181ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.153366ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.181913ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.288311ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.203992ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.428206ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.39863ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (2.096518ms) +✔ 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.067026ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (5.268096ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.007624ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.652781ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.435234ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.61294ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.799959ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.948758ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.388242ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.46722ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.170258ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.659488ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.437656ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.418608ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (64.442493ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (54.892935ms) +✔ engine: one prompt, one turn, text and usage come back (49.692748ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (361.393325ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (241.635748ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.348408ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.414968ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.967524ms) +✔ 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.927643ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.718972ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.317189ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.489047ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.586787ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.691376ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.180303ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.533128ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.885873ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.491052ms) +✔ gateway: op 9 resumable resumes (0.536362ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.821569ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.681609ms) +✔ gateway: close() is final and unparseable frames are ignored (0.392197ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (94.317873ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (58.533787ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (139.984006ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.442403ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (110.2365ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (93.031839ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (93.281368ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (196.280919ms) +✔ git: push pushes the named branch only and reports up to date (72.931362ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (78.976717ms) +✔ git: the credential helper answers get over https from a private file and nothing else (192.498725ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (746.924677ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.877264ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (82.470299ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.467376ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.535194ms) +✔ 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.667284ms) +✔ 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 (368.701422ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.422508ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.696297ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.900262ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (45.148368ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (144.394417ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (82.2528ms) +✔ notices: a kind is recorded per UTC day and found again (0.410635ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.335288ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (1.767914ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.372908ms) +✔ 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.475108ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (57.162387ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (52.573354ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (98.92471ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (665.11408ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (1.919958ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.139676ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.583988ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.734173ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.818297ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.721563ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.351536ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.819202ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.235031ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.702181ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.829086ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.098975ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.763734ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.844764ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.518365ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1017.65669ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.399851ms) +✔ 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.949877ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.228252ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.193367ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.779223ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.994224ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.81424ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (7.962176ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (8.218101ms) +✔ tools: listing and search caps hold (13.779685ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.849999ms) +✔ 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.298225ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.483929ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.991859ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.629344ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.361629ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.889447ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (4.019815ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.134085ms) +✔ 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.529497ms) +✔ 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.186143ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.230577ms) +✔ 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.75257ms) +✔ 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.254833ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 242 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3146.217285 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:113:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (1.583579ms) + 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:114: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' + } + +test at packages/cli/tests/notifier.test.mjs:189:1 +✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (153.389297ms) + AssertionError [ERR_ASSERTION]: gave-up line at +7200 s + + false !== true + + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:195:12 + at pollUntil (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:183:5) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:194:19) + 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: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-F2b.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-F2b.txt new file mode 100644 index 00000000..126ba001 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-F2b.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (27.146304ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (38.009662ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (18.5141ms) +✔ decide prints a declining choice as declining (18.628883ms) +✔ an unknown outcome is reported once and never resent (15.650932ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.287044ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (14.844071ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.900019ms) +✔ every human command refuses inside an agent run before it touches the bus (15.275902ms) +✔ usage errors exit 4; no business and no host is a usage error (13.997118ms) +✔ agents and tasks print through the broker (14.975116ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.754515ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (91.691289ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.69124ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.331019ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (51.439347ms) +✔ a business without tracker.baseUrl gets no trackers entry (65.184913ms) +✔ an unknown business and a broken system config refuse with exit 3 (77.553723ms) +✔ empty views say so (1.135468ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.399915ms) +✔ tasks print the tracker fields the snapshot carries (0.233921ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (962.551965ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (138.83737ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (75.153813ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.923681ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (124.460836ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (71.170747ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (124.842029ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (71.963873ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (136.068405ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.434272ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.119082ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (196.768748ms) +✔ bus start refuses with exit 3 without a notifier config (85.259772ms) +✔ bus start runs until bus stop; status reports it while it runs (591.2875ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.213333ms) +✔ zoned uses the IANA zone across DST (25.593634ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (28.853407ms) +✔ two blocking decisions get two DMs with different nonces (26.512062ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.340155ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (21.202885ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (25.705688ms) +✖ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.349376ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.973765ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (149.387782ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (55.829593ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.426272ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.611342ms) +✔ an inbox read failure is logged and the next poll retries (0.719418ms) +✔ no Discord id reaches the journal or the log (9.566831ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.769208ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.676623ms) +✔ the journal: a whole file that is one torn line truncates to empty (2.491676ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.554745ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.597171ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.126252ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.357005ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.532459ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.439354ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.508479ms) +✔ digest content stays within Discord's 2000 characters (0.292882ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.349787ms) +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 (355.844502ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (58.458297ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2249.525617ms) +✔ busExit and refuseInsideAgent (0.44317ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.175311ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.678547ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.627134ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.500035ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.059746ms) +✔ 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.855178ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.079715ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.047131ms) +✔ authorize: open channel, listed user (1.881817ms) +✔ authorize: wrong guild (0.199221ms) +✔ authorize: no guild (DM) (0.16743ms) +✔ authorize: unlisted channel (0.175829ms) +✔ authorize: unknown channel, no info (0.197697ms) +✔ authorize: thread of listed parent (0.185077ms) +✔ authorize: thread of unlisted parent (0.129646ms) +✔ authorize: text channel that is not a thread and not listed (0.309046ms) +✔ authorize: unlisted user (0.937763ms) +✔ authorize: no author (0.257256ms) +✔ authorize: bot author (listed id, bot flag) (0.133568ms) +✔ authorize: system author (0.123916ms) +✔ authorize: the bot itself (0.091698ms) +✔ authorize: webhook (0.102782ms) +✔ authorize: mention channel without mention (0.156143ms) +✔ authorize: mention channel with bot mention (0.753095ms) +✔ authorize: mention channel with @everyone only (0.233616ms) +✔ authorize: mention channel mentioning someone else (0.084316ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.091267ms) +✔ authorize: private thread under mention channel, mentioned (0.082723ms) +✔ authorize: private thread under mention channel, not mentioned (0.064701ms) +✔ authorize: thread in another guild per channel info (0.073075ms) +✔ authorize: not an object (0.083615ms) +✔ authorize: no id (0.067531ms) +✔ authorize: oversize content is accepted and flagged (0.103362ms) +✔ authorize: exactly the limit is not oversize (0.063306ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.54641ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.155689ms) +✔ binding: a complete binding validates and is frozen (3.013746ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.455639ms) +✔ binding: empty allowlists refuse (0.404366ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.109516ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.918847ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.501386ms) +✔ binding: file must be 0600, regular, not a symlink (1.386796ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.44632ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (106.593035ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.086633ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (383.108209ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (183.287555ms) +✔ cli: run refuses when STOP is present, before any network use (130.941356ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.626377ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.149413ms) +✔ 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.081637ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.049916ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.587682ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.14477ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.299863ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.373868ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.784768ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.620864ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.173775ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.301099ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.660229ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.305697ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.665299ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.540588ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.873305ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (8.22036ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.538817ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.74908ms) +✔ 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.939788ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.400576ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.188017ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.494767ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (7.690648ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.504002ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.967218ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.092757ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.257843ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.451567ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.750337ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.669448ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.327601ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.398387ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (64.031478ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (47.950717ms) +✔ engine: one prompt, one turn, text and usage come back (62.691644ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (352.153145ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (226.802245ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.529544ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.402728ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (124.690358ms) +✔ 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.142582ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.032089ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.341782ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.49986ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (431.522471ms) +✔ engine: a malformed JSONL line fails the turn, not the process (27.058553ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.995574ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.419962ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.71561ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.466603ms) +✔ gateway: op 9 resumable resumes (0.316897ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.961517ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.522763ms) +✔ gateway: close() is final and unparseable frames are ignored (0.362003ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (63.942108ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (59.4284ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (126.799841ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.476755ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (92.874679ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (84.349771ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (80.517048ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (184.282742ms) +✔ git: push pushes the named branch only and reports up to date (66.455619ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (84.865589ms) +✔ git: the credential helper answers get over https from a private file and nothing else (199.420563ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (739.218079ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.214298ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (80.777969ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.71809ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.816216ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (57.795153ms) +✔ 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 (333.939421ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.394607ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.228986ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.016161ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (37.755237ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (144.249473ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (86.936735ms) +✔ notices: a kind is recorded per UTC day and found again (0.463881ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.922807ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.318563ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.672544ms) +✔ 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.198484ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (50.913696ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (45.419862ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (82.671127ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (628.964212ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.388558ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.651155ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.637493ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.754378ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.86022ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.755052ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.092522ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.536178ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.111131ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.24324ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.209537ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.678632ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.778833ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.746281ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.707642ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.316703ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.447999ms) +✔ 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.354564ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.37848ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.207054ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.357033ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.783929ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.102159ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.706791ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.239873ms) +✔ tools: listing and search caps hold (10.962071ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.786456ms) +✔ 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.629923ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.284867ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.299665ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.346846ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.866692ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.853825ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.484076ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.602765ms) +✔ 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.991413ms) +✔ 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.151466ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.215167ms) +✔ 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.625186ms) +✔ 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.234811ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3086.142048 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:141:1 +✖ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.349376ms) + 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:155: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/r2/mut-F2c.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-F2c.txt new file mode 100644 index 00000000..d7f35da3 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-F2c.txt @@ -0,0 +1,290 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (31.007842ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (35.866359ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.391436ms) +✔ decide prints a declining choice as declining (19.387843ms) +✔ an unknown outcome is reported once and never resent (19.143644ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (21.741923ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.358926ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (18.612049ms) +✔ every human command refuses inside an agent run before it touches the bus (18.413219ms) +✔ usage errors exit 4; no business and no host is a usage error (18.748875ms) +✔ agents and tasks print through the broker (17.446897ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.341661ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (70.12014ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (56.660283ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (65.793117ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (74.584839ms) +✔ a business without tracker.baseUrl gets no trackers entry (75.007179ms) +✔ an unknown business and a broken system config refuse with exit 3 (77.310781ms) +✔ empty views say so (1.165592ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (3.504443ms) +✔ tasks print the tracker fields the snapshot carries (0.260713ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (983.254691ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (140.487326ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (67.116026ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (127.533849ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (122.697523ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (71.66171ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (119.158434ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (69.160923ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (140.213827ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.294332ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.685519ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (199.922757ms) +✔ bus start refuses with exit 3 without a notifier config (85.877495ms) +✔ bus start runs until bus stop; status reports it while it runs (598.438975ms) +✔ bus-service.sh renders the unit and installs it into a given directory (33.100005ms) +✔ zoned uses the IANA zone across DST (25.377136ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (29.556827ms) +✔ two blocking decisions get two DMs with different nonces (22.368874ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.267481ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (17.861226ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.111021ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (24.280414ms) +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.672223ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (158.174917ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (63.611281ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.884116ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.683243ms) +✔ an inbox read failure is logged and the next poll retries (0.775468ms) +✔ no Discord id reaches the journal or the log (10.520459ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.37734ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.169474ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.665332ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.798672ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.852685ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.641297ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.415441ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.582724ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.413852ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.570654ms) +✔ digest content stays within Discord's 2000 characters (0.350281ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.808332ms) +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 (369.692362ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (58.761125ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2284.511013ms) +✔ busExit and refuseInsideAgent (3.544211ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.079533ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.677794ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.583406ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.4371ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.715242ms) +✔ 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.759233ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.419207ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.241644ms) +✔ authorize: open channel, listed user (1.660324ms) +✔ authorize: wrong guild (0.207454ms) +✔ authorize: no guild (DM) (0.169451ms) +✔ authorize: unlisted channel (0.177561ms) +✔ authorize: unknown channel, no info (0.204644ms) +✔ authorize: thread of listed parent (0.202797ms) +✔ authorize: thread of unlisted parent (0.221182ms) +✔ authorize: text channel that is not a thread and not listed (0.114174ms) +✔ authorize: unlisted user (0.162636ms) +✔ authorize: no author (0.23081ms) +✔ authorize: bot author (listed id, bot flag) (0.133959ms) +✔ authorize: system author (0.12471ms) +✔ authorize: the bot itself (0.086604ms) +✔ authorize: webhook (0.114468ms) +✔ authorize: mention channel without mention (1.089119ms) +✔ authorize: mention channel with bot mention (0.145785ms) +✔ authorize: mention channel with @everyone only (0.189883ms) +✔ authorize: mention channel mentioning someone else (0.069336ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.08391ms) +✔ authorize: private thread under mention channel, mentioned (0.084706ms) +✔ authorize: private thread under mention channel, not mentioned (0.09793ms) +✔ authorize: thread in another guild per channel info (0.074934ms) +✔ authorize: not an object (0.0622ms) +✔ authorize: no id (0.058571ms) +✔ authorize: oversize content is accepted and flagged (0.071718ms) +✔ authorize: exactly the limit is not oversize (0.064527ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.469365ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.165844ms) +✔ binding: a complete binding validates and is frozen (2.510966ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.229942ms) +✔ binding: empty allowlists refuse (0.384401ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.510869ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.434975ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.638298ms) +✔ binding: file must be 0600, regular, not a symlink (1.767753ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.330397ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (109.04945ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.442108ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (397.439876ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (186.926601ms) +✔ cli: run refuses when STOP is present, before any network use (137.427307ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.768241ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.217834ms) +✔ 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.518944ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.017566ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.27069ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.107805ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.260684ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.164596ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.4887ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.597669ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.911539ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.046037ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.201889ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.086235ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.394232ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.377634ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.620731ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.616873ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (5.391233ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.018179ms) +✔ 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.171072ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.978544ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.662906ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.957156ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.654373ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.323015ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.928322ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.352459ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.191875ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.48947ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.479037ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.707397ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.432097ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.417485ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (59.088799ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (51.192571ms) +✔ engine: one prompt, one turn, text and usage come back (50.383243ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (355.020551ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (228.921125ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.633755ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.578243ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (127.578494ms) +✔ 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.595517ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.372594ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.318042ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.53139ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.860818ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.345959ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.570271ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.38393ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.620558ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.507665ms) +✔ gateway: op 9 resumable resumes (0.366657ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.917196ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.547144ms) +✔ gateway: close() is final and unparseable frames are ignored (0.434264ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (70.772752ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (79.984173ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (147.588211ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.489374ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (90.947522ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (78.97579ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.346823ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (187.690137ms) +✔ git: push pushes the named branch only and reports up to date (70.506242ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (88.728135ms) +✔ git: the credential helper answers get over https from a private file and nothing else (198.772126ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (733.543221ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.727733ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (92.866591ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.271926ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.955178ms) +✔ 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.79092ms) +✔ 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 (318.106323ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (3.525371ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.428769ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.918306ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (43.410185ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (134.866998ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (91.239056ms) +✔ notices: a kind is recorded per UTC day and found again (0.475589ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.387924ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.333288ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.846356ms) +✔ 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.546212ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (54.64825ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.944134ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (73.643758ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (639.914514ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.563478ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.876985ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.883849ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.046819ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.903085ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.133513ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.531952ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (3.973928ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.711905ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.708008ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.824706ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (10.641994ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.062501ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.628745ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.415296ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.325492ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.495638ms) +✔ 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.169644ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.237913ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.194271ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.287926ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.039662ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.636906ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.344053ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.921436ms) +✔ tools: listing and search caps hold (10.83548ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.886942ms) +✔ 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.785223ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.340195ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.43212ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.644596ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.085522ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.870085ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.645311ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.578727ms) +✔ 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.227949ms) +✔ 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.258363ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.227004ms) +✔ 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.49931ms) +✔ 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.252013ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 242 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3112.839501 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:113:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.111021ms) + 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:132: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:161:1 +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.672223ms) + 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:172: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/r2/mut-F2d.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-F2d.txt new file mode 100644 index 00000000..7c229ba3 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-F2d.txt @@ -0,0 +1,275 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (35.907671ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (33.828051ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (19.564022ms) +✔ decide prints a declining choice as declining (23.123139ms) +✔ an unknown outcome is reported once and never resent (16.844214ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.757332ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.102004ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (15.783173ms) +✔ every human command refuses inside an agent run before it touches the bus (18.352242ms) +✔ usage errors exit 4; no business and no host is a usage error (17.123042ms) +✔ agents and tasks print through the broker (19.02021ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.12139ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (65.167648ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.185177ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (67.754525ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (59.879169ms) +✔ a business without tracker.baseUrl gets no trackers entry (63.30319ms) +✔ an unknown business and a broken system config refuse with exit 3 (91.113368ms) +✔ empty views say so (1.042294ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.288402ms) +✔ tasks print the tracker fields the snapshot carries (0.21112ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (964.781264ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (147.235114ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (76.35978ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (125.618169ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (122.116694ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (72.92106ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (125.31243ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.404511ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (134.518737ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.183271ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.825005ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (200.699607ms) +✔ bus start refuses with exit 3 without a notifier config (85.010098ms) +✔ bus start runs until bus stop; status reports it while it runs (599.278098ms) +✔ bus-service.sh renders the unit and installs it into a given directory (33.967937ms) +✔ zoned uses the IANA zone across DST (25.819193ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (26.259947ms) +✔ two blocking decisions get two DMs with different nonces (21.336884ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.310211ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.798177ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (25.335423ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (42.562172ms) +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.796835ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (147.398441ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (74.246448ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.563056ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.075605ms) +✔ an inbox read failure is logged and the next poll retries (0.714626ms) +✔ no Discord id reaches the journal or the log (8.910446ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.238968ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.977015ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.331085ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.556547ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.93961ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.160316ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.412259ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.504315ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.424702ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.489339ms) +✔ digest content stays within Discord's 2000 characters (0.308606ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.245016ms) +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 (377.305092ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (61.886594ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2282.888322ms) +✔ busExit and refuseInsideAgent (0.396029ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.182482ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.495384ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.591696ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.516961ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.191805ms) +✔ 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.896753ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.758235ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.653996ms) +✔ authorize: open channel, listed user (1.937768ms) +✔ authorize: wrong guild (0.19303ms) +✔ authorize: no guild (DM) (0.164671ms) +✔ authorize: unlisted channel (0.215374ms) +✔ authorize: unknown channel, no info (0.178852ms) +✔ authorize: thread of listed parent (0.190196ms) +✔ authorize: thread of unlisted parent (0.249874ms) +✔ authorize: text channel that is not a thread and not listed (0.122206ms) +✔ authorize: unlisted user (0.179826ms) +✔ authorize: no author (0.25968ms) +✔ authorize: bot author (listed id, bot flag) (3.498479ms) +✔ authorize: system author (0.454962ms) +✔ authorize: the bot itself (0.10011ms) +✔ authorize: webhook (0.103339ms) +✔ authorize: mention channel without mention (0.130293ms) +✔ authorize: mention channel with bot mention (0.145646ms) +✔ authorize: mention channel with @everyone only (0.105035ms) +✔ authorize: mention channel mentioning someone else (0.099156ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.094127ms) +✔ authorize: private thread under mention channel, mentioned (0.089244ms) +✔ authorize: private thread under mention channel, not mentioned (0.07785ms) +✔ authorize: thread in another guild per channel info (0.078183ms) +✔ authorize: not an object (0.067403ms) +✔ authorize: no id (0.069164ms) +✔ authorize: oversize content is accepted and flagged (0.072886ms) +✔ authorize: exactly the limit is not oversize (0.129506ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (2.508958ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.185243ms) +✔ binding: a complete binding validates and is frozen (2.55613ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.119311ms) +✔ binding: empty allowlists refuse (0.42883ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.595041ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.080632ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.262084ms) +✔ binding: file must be 0600, regular, not a symlink (1.637671ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.313326ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (120.14796ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (5.464029ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (386.124028ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (205.191133ms) +✔ cli: run refuses when STOP is present, before any network use (130.871351ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.703233ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.089932ms) +✔ 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.387326ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (15.796369ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.699771ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.132473ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.412887ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.515809ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.367621ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.085594ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.055758ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.46701ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (44.293865ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.753552ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.05122ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.716244ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.365769ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.144234ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.834329ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.908962ms) +✔ 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.630321ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.381295ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.355993ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.782928ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.127064ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.435241ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.788725ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.508875ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.169816ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.479615ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.257794ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.635396ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.390889ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.391207ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (59.415341ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (61.451709ms) +✔ engine: one prompt, one turn, text and usage come back (49.217009ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (352.992402ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (227.904937ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.911725ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (226.980309ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (131.690081ms) +✔ 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.287434ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.728019ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.306726ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.509402ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (425.816542ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.212649ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.235066ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.780269ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.789716ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.464944ms) +✔ gateway: op 9 resumable resumes (0.390011ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.78067ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.495854ms) +✔ gateway: close() is final and unparseable frames are ignored (0.41733ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (70.383607ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (67.176795ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (152.699022ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.465056ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (94.365656ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.974451ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (84.478469ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (211.105093ms) +✔ git: push pushes the named branch only and reports up to date (65.591525ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (82.639005ms) +✔ git: the credential helper answers get over https from a private file and nothing else (196.923492ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (732.40599ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.823601ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (90.98917ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.117745ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.804433ms) +✔ 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.068597ms) +✔ 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 (330.460986ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.40533ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.944861ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.995838ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (43.039273ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (146.950752ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (84.591369ms) +✔ notices: a kind is recorded per UTC day and found again (0.440065ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.278016ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.047214ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.770043ms) +✔ 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.225454ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (50.438925ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (48.919357ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.559074ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (644.227422ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.610182ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.945677ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.025202ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.732875ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.053592ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.302412ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.850869ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.867226ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.541066ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.741019ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.919383ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.898873ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.028128ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.679448ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.291423ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.093062ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.414768ms) +✔ 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.972973ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.189231ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199192ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.219535ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.023123ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.68059ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.089698ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.554985ms) +✔ tools: listing and search caps hold (13.116831ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.705526ms) +✔ 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 (7.182148ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.260686ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.220933ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.45877ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.804999ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.046206ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.53915ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.273334ms) +✔ 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.200986ms) +✔ 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.323866ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.22155ms) +✔ 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.613232ms) +✔ 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.19972ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3114.589808 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:161:1 +✖ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.796835ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + actual - expected + + + 'refused' + - 'gave-up' + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:169: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', + expected: 'gave-up', + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-F2e.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-F2e.txt new file mode 100644 index 00000000..b2414324 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-F2e.txt @@ -0,0 +1,275 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (28.621283ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.418507ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.995275ms) +✔ decide prints a declining choice as declining (22.836862ms) +✔ an unknown outcome is reported once and never resent (15.164053ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.187221ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (21.632191ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.957876ms) +✔ every human command refuses inside an agent run before it touches the bus (15.069813ms) +✔ usage errors exit 4; no business and no host is a usage error (21.033209ms) +✔ agents and tasks print through the broker (16.857571ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.228282ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (66.96735ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (59.574632ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (57.733999ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (65.292768ms) +✔ a business without tracker.baseUrl gets no trackers entry (53.459236ms) +✔ an unknown business and a broken system config refuse with exit 3 (90.547387ms) +✔ empty views say so (1.061084ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.431715ms) +✔ tasks print the tracker fields the snapshot carries (0.217874ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (961.827261ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (144.385665ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (69.599438ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.221887ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (119.2421ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (75.874011ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (120.182931ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (74.915202ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (131.184086ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.819526ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.474489ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.010171ms) +✔ bus start refuses with exit 3 without a notifier config (83.345606ms) +✔ bus start runs until bus stop; status reports it while it runs (592.614018ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.484325ms) +✔ zoned uses the IANA zone across DST (27.698692ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (31.164826ms) +✔ two blocking decisions get two DMs with different nonces (21.953427ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.374516ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (20.330818ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (33.696536ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (24.452153ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.698773ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (147.101493ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (53.663954ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (18.172301ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.095754ms) +✔ an inbox read failure is logged and the next poll retries (0.75751ms) +✔ no Discord id reaches the journal or the log (11.053261ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.910362ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.04545ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.120175ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.461928ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.523453ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.001405ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.313132ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.453771ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.363517ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.441764ms) +✔ digest content stays within Discord's 2000 characters (0.265474ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.780231ms) +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 (361.507281ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (61.19871ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2254.738866ms) +✔ busExit and refuseInsideAgent (0.418056ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.979819ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.41399ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.536152ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.942113ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.18381ms) +✔ 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.478598ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.082299ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.086992ms) +✔ authorize: open channel, listed user (1.868361ms) +✔ authorize: wrong guild (0.339308ms) +✔ authorize: no guild (DM) (0.355702ms) +✔ authorize: unlisted channel (0.216537ms) +✔ authorize: unknown channel, no info (0.169905ms) +✔ authorize: thread of listed parent (0.18777ms) +✔ authorize: thread of unlisted parent (0.132027ms) +✔ authorize: text channel that is not a thread and not listed (0.141871ms) +✔ authorize: unlisted user (0.179529ms) +✔ authorize: no author (0.662374ms) +✔ authorize: bot author (listed id, bot flag) (0.139891ms) +✔ authorize: system author (0.13039ms) +✔ authorize: the bot itself (0.093409ms) +✔ authorize: webhook (0.159174ms) +✔ authorize: mention channel without mention (0.133873ms) +✔ authorize: mention channel with bot mention (0.158685ms) +✔ authorize: mention channel with @everyone only (0.122146ms) +✔ authorize: mention channel mentioning someone else (0.087096ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.086022ms) +✔ authorize: private thread under mention channel, mentioned (0.100617ms) +✔ authorize: private thread under mention channel, not mentioned (0.075227ms) +✔ authorize: thread in another guild per channel info (0.074536ms) +✔ authorize: not an object (0.066614ms) +✔ authorize: no id (0.086367ms) +✔ authorize: oversize content is accepted and flagged (0.075215ms) +✔ authorize: exactly the limit is not oversize (0.074069ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.44869ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (1.276891ms) +✔ binding: a complete binding validates and is frozen (2.578559ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.732603ms) +✔ binding: empty allowlists refuse (0.375719ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.323385ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.651178ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.79816ms) +✔ binding: file must be 0600, regular, not a symlink (2.016077ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.602531ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (115.297034ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.831267ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (367.802337ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (187.692232ms) +✔ cli: run refuses when STOP is present, before any network use (127.309151ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.537031ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.020843ms) +✔ 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.547014ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.720471ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.564107ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.096813ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.272855ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.61944ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.390387ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.30189ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.200616ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (2.180326ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.461218ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.916724ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.127388ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.259657ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.305258ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.722387ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.125464ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.652024ms) +✔ 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.727573ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.535223ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.050562ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.428525ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.172906ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.319807ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.79182ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.908327ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.213356ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.322505ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.381823ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (1.2548ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.445298ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.507578ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (60.985253ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (51.847787ms) +✔ engine: one prompt, one turn, text and usage come back (50.762557ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (351.806729ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (240.035379ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.509997ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (225.043251ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.43759ms) +✔ 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.54819ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.924449ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.308751ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.531149ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (425.910117ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.935939ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.315462ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.452899ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.7736ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.611255ms) +✔ gateway: op 9 resumable resumes (0.341012ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.877711ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.516388ms) +✔ gateway: close() is final and unparseable frames are ignored (0.447957ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (68.486843ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (59.495346ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.453382ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.458105ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (89.916991ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (75.23857ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (85.488293ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (192.418581ms) +✔ git: push pushes the named branch only and reports up to date (66.165731ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (77.232416ms) +✔ git: the credential helper answers get over https from a private file and nothing else (196.747174ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (731.926544ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.755713ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (82.249044ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.127505ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.18822ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (57.270022ms) +✔ 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 (333.069339ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.454178ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.855622ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.979474ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (34.582006ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (129.468697ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (79.569227ms) +✔ notices: a kind is recorded per UTC day and found again (0.411317ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (8.930572ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.046757ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.780801ms) +✔ 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.67267ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (52.373179ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.581135ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (69.44625ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (614.78035ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.500519ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.121771ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.691956ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.707289ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.874534ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.147746ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.489601ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.579195ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.59566ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.720314ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.519307ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.762805ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.01743ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.796999ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.226122ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1012.079624ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.444775ms) +✔ 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.03868ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.289155ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.206254ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.316366ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.437806ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.630721ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.564635ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.425491ms) +✔ tools: listing and search caps hold (11.546249ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.435662ms) +✔ tools: the read uses the checked file itself; a symlink, a swapped file, a FIFO, a grown file or a hard link at read time is refused (13.51128ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (0.972228ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.345303ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.886211ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.701352ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.690755ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.529186ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.258573ms) +✔ 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.389937ms) +✔ 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.242156ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.226014ms) +✔ 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.526761ms) +✔ 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.78514ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3082.015709 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:113:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (33.696536ms) + AssertionError [ERR_ASSERTION]: The input did not match the regular expression /\[blocking, DM refused, not retried\] a8df1921 git\.push\.protected/. Input: + + 'Mosaic digest (demo, 2026-10-08): 1 open decision(s).\n' + + '- [blocking, DM pending] a8df1921 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:138: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] a8df1921 git.push.protected: Push the release?\nRun mosaic inbox for the full list.', + expected: /\[blocking, DM refused, not retried\] a8df1921 git\.push\.protected/, + operator: 'match', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-F2f.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-F2f.txt new file mode 100644 index 00000000..74b36c1c --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-F2f.txt @@ -0,0 +1,300 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (56.885504ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.287014ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (17.265434ms) +✔ decide prints a declining choice as declining (14.985414ms) +✔ an unknown outcome is reported once and never resent (16.212657ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (14.862663ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (14.512272ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (22.653118ms) +✔ every human command refuses inside an agent run before it touches the bus (24.838053ms) +✔ usage errors exit 4; no business and no host is a usage error (18.465952ms) +✔ agents and tasks print through the broker (14.697332ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.42752ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (74.09765ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.52387ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (56.072894ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (52.204553ms) +✔ a business without tracker.baseUrl gets no trackers entry (63.35558ms) +✔ an unknown business and a broken system config refuse with exit 3 (79.893582ms) +✔ empty views say so (1.21304ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.380745ms) +✔ tasks print the tracker fields the snapshot carries (0.22152ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (942.790779ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (155.209653ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (75.197442ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (123.043863ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (121.441269ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (81.126446ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (127.683225ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (86.137999ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (131.315693ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.172735ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.028145ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (202.631136ms) +✔ bus start refuses with exit 3 without a notifier config (83.066616ms) +✔ bus start runs until bus stop; status reports it while it runs (602.933961ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.736454ms) +✔ zoned uses the IANA zone across DST (26.367569ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (61.280969ms) +✔ two blocking decisions get two DMs with different nonces (20.343789ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.326101ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (21.98434ms) +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (19.818272ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.372ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.619543ms) +✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (139.966927ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (49.035235ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (14.995437ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (13.299279ms) +✔ an inbox read failure is logged and the next poll retries (0.773214ms) +✔ no Discord id reaches the journal or the log (11.954278ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.146364ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.010685ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.809056ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.57811ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.710816ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.548265ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.388793ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.528707ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.413836ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.505336ms) +✔ digest content stays within Discord's 2000 characters (0.31769ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.5451ms) +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 (350.945743ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (51.215414ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2235.504837ms) +✔ busExit and refuseInsideAgent (0.45267ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.985741ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.678746ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.640068ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.470898ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.894024ms) +✔ 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.126932ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.108522ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.147988ms) +✔ authorize: open channel, listed user (1.828706ms) +✔ authorize: wrong guild (0.185022ms) +✔ authorize: no guild (DM) (0.177938ms) +✔ authorize: unlisted channel (0.211176ms) +✔ authorize: unknown channel, no info (0.184219ms) +✔ authorize: thread of listed parent (0.178391ms) +✔ authorize: thread of unlisted parent (0.177869ms) +✔ authorize: text channel that is not a thread and not listed (0.14313ms) +✔ authorize: unlisted user (0.182067ms) +✔ authorize: no author (0.294058ms) +✔ authorize: bot author (listed id, bot flag) (0.171007ms) +✔ authorize: system author (0.126633ms) +✔ authorize: the bot itself (0.0969ms) +✔ authorize: webhook (0.088551ms) +✔ authorize: mention channel without mention (0.137281ms) +✔ authorize: mention channel with bot mention (0.128178ms) +✔ authorize: mention channel with @everyone only (0.118136ms) +✔ authorize: mention channel mentioning someone else (0.095369ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.086469ms) +✔ authorize: private thread under mention channel, mentioned (0.084808ms) +✔ authorize: private thread under mention channel, not mentioned (0.068016ms) +✔ authorize: thread in another guild per channel info (0.072532ms) +✔ authorize: not an object (0.062472ms) +✔ authorize: no id (0.060983ms) +✔ authorize: oversize content is accepted and flagged (0.066501ms) +✔ authorize: exactly the limit is not oversize (0.062761ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.455448ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.138271ms) +✔ binding: a complete binding validates and is frozen (2.508739ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.24338ms) +✔ binding: empty allowlists refuse (0.387825ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.288192ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.587089ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.442067ms) +✔ binding: file must be 0600, regular, not a symlink (1.614418ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.442121ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (108.988243ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.913193ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (385.981748ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (198.132962ms) +✔ cli: run refuses when STOP is present, before any network use (125.979165ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.608937ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.173621ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (15.874528ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.952335ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.473261ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.592667ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.341225ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.70334ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.782675ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.044352ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.374301ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.042096ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.734104ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.251025ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.473614ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (9.356673ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.261338ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (9.648391ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.555416ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.771529ms) +✔ 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.321657ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (4.769728ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (7.345102ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.643734ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.470697ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.347587ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.797221ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.765022ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.185081ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.362878ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.149406ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.642868ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.960776ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.43147ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (49.204733ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (47.370258ms) +✔ engine: one prompt, one turn, text and usage come back (45.890133ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (349.744044ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (230.082374ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.185494ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.972299ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.172254ms) +✔ 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.782508ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (616.080922ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.279921ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.544898ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.71944ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.42561ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (43.133759ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.881829ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.556568ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.536634ms) +✔ gateway: op 9 resumable resumes (0.545814ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.870018ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.598496ms) +✔ gateway: close() is final and unparseable frames are ignored (0.619697ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (65.19501ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (58.813551ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (138.390132ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.312712ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (94.387699ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (94.891888ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (86.79196ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (203.185544ms) +✔ git: push pushes the named branch only and reports up to date (64.081443ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (86.495759ms) +✔ git: the credential helper answers get over https from a private file and nothing else (209.998925ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (733.972789ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.091593ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (82.294687ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.166123ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.122215ms) +✔ 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.012183ms) +✔ 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.721233ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.396141ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.525606ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.995717ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (34.459796ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (148.210668ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (82.366295ms) +✔ notices: a kind is recorded per UTC day and found again (0.472883ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.238448ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.216863ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.639008ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (52.236586ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (52.83315ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (47.624887ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (79.969359ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (679.403112ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.421208ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.031282ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.640044ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.703364ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.817813ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.170314ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.858315ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.405603ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.326149ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.642422ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.030959ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.350764ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.986666ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.607414ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.468382ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.286571ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.464161ms) +✔ 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.485864ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.537642ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199155ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.324383ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.380325ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.196114ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.302093ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.673284ms) +✔ tools: listing and search caps hold (12.447422ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.895993ms) +✔ 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.84361ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (2.269688ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.375267ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.547906ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.774322ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.854376ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (5.010109ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.513761ms) +✔ 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.900527ms) +✔ 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.378903ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.23307ms) +✔ 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.234016ms) +✔ 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.811915ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 242 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3114.074441 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:113:1 +✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (19.818272ms) + 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:127: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' + } + +test at packages/cli/tests/notifier.test.mjs:189:1 +✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (139.966927ms) + AssertionError [ERR_ASSERTION]: gave-up line at +7200 s + + false !== true + + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:195:12 + at pollUntil (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:183:5) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:194:19) + 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: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-G144.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-G144.txt new file mode 100644 index 00000000..db79ef7d --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-G144.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (29.095741ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.923888ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (15.985505ms) +✔ decide prints a declining choice as declining (20.314714ms) +✔ an unknown outcome is reported once and never resent (13.899644ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.224943ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.276258ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.604299ms) +✔ every human command refuses inside an agent run before it touches the bus (19.184063ms) +✔ usage errors exit 4; no business and no host is a usage error (19.317868ms) +✔ agents and tasks print through the broker (17.139908ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.565535ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (72.467314ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (49.84195ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (52.10819ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (62.053938ms) +✔ a business without tracker.baseUrl gets no trackers entry (62.308204ms) +✔ an unknown business and a broken system config refuse with exit 3 (87.000304ms) +✔ empty views say so (1.115034ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.454864ms) +✔ tasks print the tracker fields the snapshot carries (0.226238ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (950.497988ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (141.486172ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (74.041411ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (127.53729ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (126.267882ms) +✖ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.29616ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (119.74423ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (69.877328ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (131.504147ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.031934ms) +✔ bus stop refuses to signal a live pid that is not a bus host (201.922926ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (201.441639ms) +✔ bus start refuses with exit 3 without a notifier config (87.594342ms) +✔ bus start runs until bus stop; status reports it while it runs (594.89957ms) +✔ bus-service.sh renders the unit and installs it into a given directory (33.862868ms) +✔ zoned uses the IANA zone across DST (27.366217ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (30.324315ms) +✔ two blocking decisions get two DMs with different nonces (20.355728ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.27787ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (24.206386ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (26.174087ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.841159ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.824088ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (152.302442ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (51.172318ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (14.327499ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.355138ms) +✔ an inbox read failure is logged and the next poll retries (0.718549ms) +✔ no Discord id reaches the journal or the log (9.433057ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.174015ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.297969ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.184247ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.484994ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.640183ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.202926ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.320092ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.460622ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.390474ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.437026ms) +✔ digest content stays within Discord's 2000 characters (0.268666ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.030194ms) +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 (369.137409ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (61.50929ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2249.586124ms) +✔ busExit and refuseInsideAgent (0.41445ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.137569ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.194828ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.586076ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.51967ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (15.685404ms) +✔ 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.000277ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.734034ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.779899ms) +✔ authorize: open channel, listed user (1.969847ms) +✔ authorize: wrong guild (0.198678ms) +✔ authorize: no guild (DM) (0.197448ms) +✔ authorize: unlisted channel (0.182072ms) +✔ authorize: unknown channel, no info (0.182055ms) +✔ authorize: thread of listed parent (0.210424ms) +✔ authorize: thread of unlisted parent (0.156645ms) +✔ authorize: text channel that is not a thread and not listed (0.137805ms) +✔ authorize: unlisted user (0.18579ms) +✔ authorize: no author (0.296704ms) +✔ authorize: bot author (listed id, bot flag) (0.145194ms) +✔ authorize: system author (0.312494ms) +✔ authorize: the bot itself (0.094985ms) +✔ authorize: webhook (0.752715ms) +✔ authorize: mention channel without mention (0.134448ms) +✔ authorize: mention channel with bot mention (0.116537ms) +✔ authorize: mention channel with @everyone only (0.083491ms) +✔ authorize: mention channel mentioning someone else (0.077775ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.082699ms) +✔ authorize: private thread under mention channel, mentioned (0.088658ms) +✔ authorize: private thread under mention channel, not mentioned (0.073754ms) +✔ authorize: thread in another guild per channel info (0.088674ms) +✔ authorize: not an object (0.054784ms) +✔ authorize: no id (0.063928ms) +✔ authorize: oversize content is accepted and flagged (0.074415ms) +✔ authorize: exactly the limit is not oversize (0.073527ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (2.03148ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.140374ms) +✔ binding: a complete binding validates and is frozen (2.424948ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.243753ms) +✔ binding: empty allowlists refuse (0.377074ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.271095ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.481653ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.552278ms) +✔ binding: file must be 0600, regular, not a symlink (2.326417ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.594778ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (108.406928ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.781137ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (402.476611ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (187.532858ms) +✔ cli: run refuses when STOP is present, before any network use (125.515579ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.741472ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.461955ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (16.566057ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.85597ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.728884ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.456175ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (2.03462ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.702603ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.958556ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.860861ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.012946ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.352594ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.615618ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.052034ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.503955ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.821725ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.474703ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.069742ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.144499ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.741144ms) +✔ 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.842408ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.432428ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.965582ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.973037ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.621111ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.447485ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.819355ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.235456ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.253733ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.55866ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.282132ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.637805ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.643009ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.422296ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (52.264335ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (52.951878ms) +✔ engine: one prompt, one turn, text and usage come back (57.502895ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (382.727095ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (228.256983ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (114.165961ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (227.288806ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.812203ms) +✔ 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.627369ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.402369ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.226298ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.508602ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.083474ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.897817ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.112548ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.412089ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.631675ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.449641ms) +✔ gateway: op 9 resumable resumes (0.348812ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.929052ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.611223ms) +✔ gateway: close() is final and unparseable frames are ignored (0.45368ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (67.794757ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (62.659566ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (128.676422ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.366912ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (89.399851ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (83.745301ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (77.88815ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (199.054856ms) +✔ git: push pushes the named branch only and reports up to date (65.829609ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (76.923938ms) +✔ git: the credential helper answers get over https from a private file and nothing else (194.349209ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (733.403237ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.521431ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (83.973747ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.295484ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.099574ms) +✔ 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.940635ms) +✔ 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 (317.910765ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.409595ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.29891ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.056676ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (35.587816ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (135.680834ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (82.764193ms) +✔ notices: a kind is recorded per UTC day and found again (0.408213ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.075093ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.182406ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.7002ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.868684ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (58.914471ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (42.049844ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (88.275118ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (626.350216ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.622214ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.062453ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.240388ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.715139ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.852064ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.335935ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.818495ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.779181ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.000309ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.904269ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.925509ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.554198ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.364328ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.632652ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.753915ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.403495ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.42263ms) +✔ 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.964611ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.225257ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.217242ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.34837ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.074763ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.775756ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.131013ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.821373ms) +✔ tools: listing and search caps hold (12.725447ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.936192ms) +✔ 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.826119ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.864891ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.6133ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.489664ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.861216ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.853406ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.892454ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.799945ms) +✔ 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.586208ms) +✔ web: web_fetch returns html as text with the title, follows an https redirect, keeps plain text and json, and cuts at the cap (6.060918ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.356494ms) +✔ 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.239184ms) +✔ 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.209551ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3096.592038 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:243:1 +✖ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.29616ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 1 !== 0 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:268: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: 1, + expected: 0, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-G144cb.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-G144cb.txt new file mode 100644 index 00000000..fe008ff0 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-G144cb.txt @@ -0,0 +1,284 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (26.1368ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (46.745011ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (16.100877ms) +✔ decide prints a declining choice as declining (16.379141ms) +✔ an unknown outcome is reported once and never resent (17.447369ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (14.858296ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (14.287926ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (15.549323ms) +✔ every human command refuses inside an agent run before it touches the bus (16.259379ms) +✔ usage errors exit 4; no business and no host is a usage error (21.570527ms) +✔ agents and tasks print through the broker (16.178902ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.259346ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (74.942581ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.961992ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (54.682985ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (60.085701ms) +✔ a business without tracker.baseUrl gets no trackers entry (58.386835ms) +✔ an unknown business and a broken system config refuse with exit 3 (81.737818ms) +✔ empty views say so (0.967447ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.493325ms) +✔ tasks print the tracker fields the snapshot carries (0.215181ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (957.7209ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (146.582909ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (76.136108ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.968102ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (118.781911ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (69.989998ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (122.555871ms) +✖ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (70.183687ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (133.670931ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (26.088198ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.173423ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (205.297138ms) +✔ bus start refuses with exit 3 without a notifier config (84.370611ms) +✔ bus start runs until bus stop; status reports it while it runs (592.037185ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.73236ms) +✔ zoned uses the IANA zone across DST (27.008417ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (23.208353ms) +✔ two blocking decisions get two DMs with different nonces (19.924515ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.451612ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.04242ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (21.225929ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (24.649025ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (16.345159ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (140.119999ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (62.387294ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (14.681064ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (14.434875ms) +✔ an inbox read failure is logged and the next poll retries (0.837843ms) +✔ no Discord id reaches the journal or the log (12.389468ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.696068ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (4.681866ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.93143ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.809339ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.819608ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.484426ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.44415ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.566352ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.441192ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.525ms) +✔ digest content stays within Discord's 2000 characters (0.539639ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.651376ms) +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 (360.952342ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (53.7094ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2240.251825ms) +✔ busExit and refuseInsideAgent (0.416736ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.308892ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.522903ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.645219ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.483178ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.376906ms) +✔ 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.457576ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.343405ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.979697ms) +✔ authorize: open channel, listed user (1.743225ms) +✔ authorize: wrong guild (0.189864ms) +✔ authorize: no guild (DM) (0.268705ms) +✔ authorize: unlisted channel (0.171038ms) +✔ authorize: unknown channel, no info (0.150431ms) +✔ authorize: thread of listed parent (0.183056ms) +✔ authorize: thread of unlisted parent (0.13136ms) +✔ authorize: text channel that is not a thread and not listed (0.118599ms) +✔ authorize: unlisted user (1.06446ms) +✔ authorize: no author (0.237253ms) +✔ authorize: bot author (listed id, bot flag) (0.593525ms) +✔ authorize: system author (0.125774ms) +✔ authorize: the bot itself (0.116418ms) +✔ authorize: webhook (0.077649ms) +✔ authorize: mention channel without mention (0.12093ms) +✔ authorize: mention channel with bot mention (0.117308ms) +✔ authorize: mention channel with @everyone only (0.089981ms) +✔ authorize: mention channel mentioning someone else (0.097472ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.114099ms) +✔ authorize: private thread under mention channel, mentioned (0.934377ms) +✔ authorize: private thread under mention channel, not mentioned (0.10492ms) +✔ authorize: thread in another guild per channel info (0.08405ms) +✔ authorize: not an object (0.07059ms) +✔ authorize: no id (0.067651ms) +✔ authorize: oversize content is accepted and flagged (0.075171ms) +✔ authorize: exactly the limit is not oversize (0.064778ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.469229ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.138129ms) +✔ binding: a complete binding validates and is frozen (2.667906ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.00733ms) +✔ binding: empty allowlists refuse (0.429084ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.30046ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.540602ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.471465ms) +✔ binding: file must be 0600, regular, not a symlink (1.866653ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.336394ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (103.007293ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.545742ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (395.75902ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (190.884765ms) +✔ cli: run refuses when STOP is present, before any network use (134.217422ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.734418ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.181598ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (15.455274ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.43262ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.673261ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.407837ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.500103ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.579746ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.438276ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.533333ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.990361ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.394028ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (41.831246ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.960726ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.141976ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.363426ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.774124ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.97473ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.172664ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.817185ms) +✔ 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.004484ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.134845ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.717394ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.680791ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.720045ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.528464ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.787622ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.868034ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.237296ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (2.345611ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.179714ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.653019ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.542918ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.394399ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (51.982367ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (54.041696ms) +✔ engine: one prompt, one turn, text and usage come back (52.543706ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (365.980906ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (226.717036ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (114.841503ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.378286ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.395957ms) +✔ 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.982971ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.502266ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.28494ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.455757ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (425.889445ms) +✔ engine: a malformed JSONL line fails the turn, not the process (26.144857ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.886129ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.475985ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.50105ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.447689ms) +✔ gateway: op 9 resumable resumes (0.359406ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.904707ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.512303ms) +✔ gateway: close() is final and unparseable frames are ignored (0.40511ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (69.338568ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (59.822598ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (139.054817ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.443794ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (97.524835ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.910796ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.886667ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (189.922796ms) +✔ git: push pushes the named branch only and reports up to date (65.437666ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (84.476317ms) +✔ git: the credential helper answers get over https from a private file and nothing else (189.720326ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (734.069404ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.48591ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (81.743585ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.043907ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.96132ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (57.800739ms) +✔ 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 (340.613444ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.369071ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.297509ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.978547ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (36.117537ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (137.026522ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (81.489355ms) +✔ notices: a kind is recorded per UTC day and found again (0.459774ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.033554ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.574369ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.694562ms) +✔ 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.965925ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (46.456693ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.562243ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (76.745919ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (632.318303ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.507474ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.51626ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.641018ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.735424ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.814947ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.569093ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.671653ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.797279ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.193978ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.796964ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.823469ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.984046ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.841136ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.603736ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.303258ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.621822ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.452632ms) +✔ 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.514694ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.337529ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.210321ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.898733ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.162243ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.90781ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.823158ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.699656ms) +✔ tools: listing and search caps hold (12.142446ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.913146ms) +✔ 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.991941ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.311991ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.285283ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.303826ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.770847ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.446206ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.261044ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.095035ms) +✔ 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.890644ms) +✔ 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.623231ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.271071ms) +✔ 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.880563ms) +✔ 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.241811ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 21892.726774 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:282:1 +✖ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (70.183687ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: write EPIPE + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:298:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: Error: write EPIPE + at epipe (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:113:24) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:288:35 + at ChildProcess.send (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:94:23) + at startHost (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/host.mjs:144:36) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async waitForActual (node:assert:615:5) + at async strict.rejects (node:assert:738:25) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:298:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7), + operator: 'rejects', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-G150.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-G150.txt new file mode 100644 index 00000000..b290a026 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-G150.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (36.137224ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (32.80617ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.446641ms) +✔ decide prints a declining choice as declining (22.61046ms) +✔ an unknown outcome is reported once and never resent (18.116597ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (14.993634ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (16.118092ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.030829ms) +✔ every human command refuses inside an agent run before it touches the bus (16.339592ms) +✔ usage errors exit 4; no business and no host is a usage error (16.636658ms) +✔ agents and tasks print through the broker (23.719494ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.803717ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (67.682761ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.452436ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (60.125592ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (55.93854ms) +✔ a business without tracker.baseUrl gets no trackers entry (63.122419ms) +✔ an unknown business and a broken system config refuse with exit 3 (82.102685ms) +✔ empty views say so (1.078813ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.428027ms) +✔ tasks print the tracker fields the snapshot carries (0.206948ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (952.526967ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (154.740474ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (78.082734ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.007999ms) +✖ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (134.711417ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (86.036456ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (124.9122ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.737395ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (138.083174ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (21.677089ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.200301ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (199.31374ms) +✔ bus start refuses with exit 3 without a notifier config (85.678937ms) +✔ bus start runs until bus stop; status reports it while it runs (598.861125ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.648972ms) +✔ zoned uses the IANA zone across DST (25.186616ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (31.115707ms) +✔ two blocking decisions get two DMs with different nonces (23.120288ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.295168ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.592593ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (34.223143ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.865874ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (15.390973ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (149.241935ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (61.050772ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.366519ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.444671ms) +✔ an inbox read failure is logged and the next poll retries (0.794404ms) +✔ no Discord id reaches the journal or the log (9.407856ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.757199ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.164529ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.329633ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.606127ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.762642ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.845129ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.623139ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.570491ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.434198ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.62767ms) +✔ digest content stays within Discord's 2000 characters (0.337211ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.23101ms) +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 (357.441666ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (66.744628ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2257.702429ms) +✔ busExit and refuseInsideAgent (0.401325ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.122055ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.395797ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.557231ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.470742ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.128501ms) +✔ 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.948692ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.001664ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.839765ms) +✔ authorize: open channel, listed user (1.905814ms) +✔ authorize: wrong guild (0.194095ms) +✔ authorize: no guild (DM) (0.331274ms) +✔ authorize: unlisted channel (0.186257ms) +✔ authorize: unknown channel, no info (0.255594ms) +✔ authorize: thread of listed parent (0.175622ms) +✔ authorize: thread of unlisted parent (0.24057ms) +✔ authorize: text channel that is not a thread and not listed (0.146466ms) +✔ authorize: unlisted user (1.074803ms) +✔ authorize: no author (0.480332ms) +✔ authorize: bot author (listed id, bot flag) (0.227986ms) +✔ authorize: system author (0.118093ms) +✔ authorize: the bot itself (0.088781ms) +✔ authorize: webhook (0.095951ms) +✔ authorize: mention channel without mention (0.125265ms) +✔ authorize: mention channel with bot mention (0.1328ms) +✔ authorize: mention channel with @everyone only (0.100235ms) +✔ authorize: mention channel mentioning someone else (0.150046ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.104864ms) +✔ authorize: private thread under mention channel, mentioned (0.100161ms) +✔ authorize: private thread under mention channel, not mentioned (0.088155ms) +✔ authorize: thread in another guild per channel info (0.079789ms) +✔ authorize: not an object (0.072272ms) +✔ authorize: no id (0.12767ms) +✔ authorize: oversize content is accepted and flagged (0.079619ms) +✔ authorize: exactly the limit is not oversize (0.074017ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.465493ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.146439ms) +✔ binding: a complete binding validates and is frozen (3.168897ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.552052ms) +✔ binding: empty allowlists refuse (0.522861ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.345365ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.735368ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.189422ms) +✔ binding: file must be 0600, regular, not a symlink (1.825939ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.850965ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (109.72649ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.939662ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (382.730718ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (209.193251ms) +✔ cli: run refuses when STOP is present, before any network use (135.875836ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.16764ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (2.42409ms) +✔ 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.243776ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.980429ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.535753ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.148879ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.529248ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.88324ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.555626ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.226429ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.11603ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.054395ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.21392ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.449019ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.234295ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.867423ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.231827ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.131194ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.656934ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.787371ms) +✔ 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.163094ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (6.415917ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.456546ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.055967ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.92862ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.694643ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.781465ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.875644ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.209294ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (2.804381ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (4.91737ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.813091ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.566532ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.636779ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (61.082941ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (53.986925ms) +✔ engine: one prompt, one turn, text and usage come back (49.689667ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (357.99446ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.366643ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.917301ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.778841ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.503987ms) +✔ 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.70331ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.106109ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.275196ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.53512ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.244086ms) +✔ engine: a malformed JSONL line fails the turn, not the process (30.163175ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.049539ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.741781ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.527813ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.754199ms) +✔ gateway: op 9 resumable resumes (0.359216ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.906629ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.503822ms) +✔ gateway: close() is final and unparseable frames are ignored (0.432442ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (69.647007ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (65.493273ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (120.122151ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.424613ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (94.670584ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (78.869019ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (86.12904ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (218.325974ms) +✔ git: push pushes the named branch only and reports up to date (81.564606ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (95.476163ms) +✔ git: the credential helper answers get over https from a private file and nothing else (210.664251ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (753.956112ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.227289ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (88.618108ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.186297ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.277209ms) +✔ 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.174792ms) +✔ 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 (333.275407ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.377945ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.615643ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.033357ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (44.387525ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (155.641805ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (104.850567ms) +✔ notices: a kind is recorded per UTC day and found again (0.497782ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (3.731311ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (1.79075ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.825007ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.237049ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (50.659425ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (47.28091ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (74.125674ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (704.036932ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.316203ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.632113ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.610573ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (1.213551ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.407844ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.684309ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.034095ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.741239ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.165727ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.55187ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.232172ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.824441ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (6.488054ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.659137ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.330052ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.245534ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.604235ms) +✔ 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.280297ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.252352ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.363548ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.277544ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.343008ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.452761ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.450104ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.691864ms) +✔ tools: listing and search caps hold (9.44943ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.738477ms) +✔ 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.475546ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.280617ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.474486ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.151292ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.23079ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.745754ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.97183ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.224102ms) +✔ 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.702489ms) +✔ 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.230836ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.261957ms) +✔ 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.620959ms) +✔ 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.493582ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3139.768113 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:219:1 +✖ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (134.711417ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 1 !== 0 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:240: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: 1, + expected: 0, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-G150cb.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-G150cb.txt new file mode 100644 index 00000000..dccd5b7b --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-G150cb.txt @@ -0,0 +1,284 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (26.600514ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.705587ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (16.769242ms) +✔ decide prints a declining choice as declining (15.135272ms) +✔ an unknown outcome is reported once and never resent (15.031737ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.435094ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.768125ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.572267ms) +✔ every human command refuses inside an agent run before it touches the bus (15.354229ms) +✔ usage errors exit 4; no business and no host is a usage error (21.055411ms) +✔ agents and tasks print through the broker (23.168222ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.376503ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (64.768109ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (52.772937ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (53.69811ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (55.925369ms) +✔ a business without tracker.baseUrl gets no trackers entry (56.035949ms) +✔ an unknown business and a broken system config refuse with exit 3 (92.967208ms) +✔ empty views say so (0.953993ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.154696ms) +✔ tasks print the tracker fields the snapshot carries (0.221803ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (962.276869ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (143.48731ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (70.299392ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (126.799875ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.002067ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.269733ms) +✖ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (141.222031ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (73.507758ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (131.554124ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.680169ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.905447ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (194.944408ms) +✔ bus start refuses with exit 3 without a notifier config (85.043443ms) +✔ bus start runs until bus stop; status reports it while it runs (594.029709ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.50059ms) +✔ zoned uses the IANA zone across DST (29.044084ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (27.900183ms) +✔ two blocking decisions get two DMs with different nonces (20.115312ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.286355ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.363176ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (22.232889ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (21.679329ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (20.100012ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (144.371044ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (58.039443ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.025702ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.831656ms) +✔ an inbox read failure is logged and the next poll retries (0.64579ms) +✔ no Discord id reaches the journal or the log (11.503299ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.144653ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.265131ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.494834ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.682916ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.681227ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.011013ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.337943ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.522107ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.371251ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.462659ms) +✔ digest content stays within Discord's 2000 characters (0.279541ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.507806ms) +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 (359.906235ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (59.911226ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2254.18594ms) +✔ busExit and refuseInsideAgent (0.39255ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.97925ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.362717ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.55241ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.464538ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (14.744728ms) +✔ 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.923578ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.872745ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.252777ms) +✔ authorize: open channel, listed user (1.698945ms) +✔ authorize: wrong guild (0.1847ms) +✔ authorize: no guild (DM) (0.15856ms) +✔ authorize: unlisted channel (0.215869ms) +✔ authorize: unknown channel, no info (0.189214ms) +✔ authorize: thread of listed parent (0.143098ms) +✔ authorize: thread of unlisted parent (0.123843ms) +✔ authorize: text channel that is not a thread and not listed (0.095182ms) +✔ authorize: unlisted user (0.162611ms) +✔ authorize: no author (0.271775ms) +✔ authorize: bot author (listed id, bot flag) (0.147219ms) +✔ authorize: system author (0.116192ms) +✔ authorize: the bot itself (0.066576ms) +✔ authorize: webhook (0.077195ms) +✔ authorize: mention channel without mention (0.142467ms) +✔ authorize: mention channel with bot mention (0.132994ms) +✔ authorize: mention channel with @everyone only (0.117532ms) +✔ authorize: mention channel mentioning someone else (0.076602ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.106192ms) +✔ authorize: private thread under mention channel, mentioned (0.062647ms) +✔ authorize: private thread under mention channel, not mentioned (0.040465ms) +✔ authorize: thread in another guild per channel info (0.042334ms) +✔ authorize: not an object (0.058914ms) +✔ authorize: no id (0.062515ms) +✔ authorize: oversize content is accepted and flagged (0.069489ms) +✔ authorize: exactly the limit is not oversize (0.069454ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.561842ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.149422ms) +✔ binding: a complete binding validates and is frozen (2.4084ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.368569ms) +✔ binding: empty allowlists refuse (0.374772ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.468965ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.6009ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.92465ms) +✔ binding: file must be 0600, regular, not a symlink (1.615372ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.411698ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (106.654393ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (6.38343ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (403.239559ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (180.539463ms) +✔ cli: run refuses when STOP is present, before any network use (118.674373ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.816907ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.367893ms) +✔ 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.692579ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.058789ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.374568ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.107055ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.261762ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.934659ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.277506ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.171679ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.378805ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.154108ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.159283ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.356294ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.220538ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.597752ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.570117ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.052237ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.873245ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (2.223108ms) +✔ 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.623839ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.797385ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.379171ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.834078ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.345558ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.5404ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.759062ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.97261ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.173579ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.33179ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.233988ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.672229ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.400304ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.415409ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (46.132355ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (56.214513ms) +✔ engine: one prompt, one turn, text and usage come back (53.595986ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (365.304662ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (226.989441ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (112.933314ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (227.476041ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.467088ms) +✔ 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.312892ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.425816ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.242552ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.45962ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.337404ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.377686ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.338052ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.530913ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.794592ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.478666ms) +✔ gateway: op 9 resumable resumes (0.332182ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.763926ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.432209ms) +✔ gateway: close() is final and unparseable frames are ignored (0.382755ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (68.524866ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (63.94703ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (136.516427ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.527505ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (91.998853ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (88.154245ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (88.624448ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (186.984633ms) +✔ git: push pushes the named branch only and reports up to date (63.999475ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (81.504909ms) +✔ git: the credential helper answers get over https from a private file and nothing else (189.19431ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (752.117747ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (3.995149ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (84.923031ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.893848ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.096948ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (57.919952ms) +✔ 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 (335.710733ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.356444ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.858589ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.00731ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.905088ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (132.683955ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (78.784082ms) +✔ notices: a kind is recorded per UTC day and found again (0.42906ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.314265ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.029434ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.771295ms) +✔ 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.344136ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (49.411468ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (45.986284ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (73.96153ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (631.660709ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.746742ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.212156ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.81499ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.734504ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.931204ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.372629ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.548172ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.40931ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.034413ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (32.390237ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (16.599589ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.916278ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.815668ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.748293ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.21768ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.152052ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.428072ms) +✔ 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.8981ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.208583ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.203371ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.229417ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (7.811889ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.532553ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.825032ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.437295ms) +✔ tools: listing and search caps hold (15.185943ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.963757ms) +✔ 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.725773ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.280921ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.069305ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.297714ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.666493ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.866652ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.112619ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.209453ms) +✔ 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.690379ms) +✔ 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.100777ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.217931ms) +✔ 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.551203ms) +✔ 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.197871ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 21848.550915 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:271:1 +✖ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (141.222031ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: write EPIPE + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:279:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: Error: write EPIPE + at epipe (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:113:24) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:276:51 + at ChildProcess.send (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:94:23) + at startHost (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/host.mjs:150:36) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async waitForActual (node:assert:615:5) + at async strict.rejects (node:assert:738:25) + at async TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:279:3) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7), + operator: 'rejects', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-G184.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-G184.txt new file mode 100644 index 00000000..7dc1d0c7 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-G184.txt @@ -0,0 +1,272 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (30.190261ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.551942ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (19.199556ms) +✔ decide prints a declining choice as declining (17.919345ms) +✔ an unknown outcome is reported once and never resent (21.964825ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (26.414966ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.731365ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (23.234187ms) +✔ every human command refuses inside an agent run before it touches the bus (18.288267ms) +✔ usage errors exit 4; no business and no host is a usage error (21.811173ms) +✔ agents and tasks print through the broker (16.927124ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.522903ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (77.396725ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (60.485574ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (56.355736ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (63.169257ms) +✔ a business without tracker.baseUrl gets no trackers entry (60.166926ms) +✔ an unknown business and a broken system config refuse with exit 3 (95.495542ms) +✔ empty views say so (1.140766ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.363868ms) +✔ tasks print the tracker fields the snapshot carries (0.220362ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (971.553376ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (159.429205ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (77.209615ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (129.794763ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (125.291949ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (87.645136ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (121.483554ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (90.276229ms) +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (138.303819ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.158056ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.221938ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (214.257266ms) +✔ bus start refuses with exit 3 without a notifier config (87.538076ms) +✔ bus start runs until bus stop; status reports it while it runs (604.522432ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.482392ms) +✔ zoned uses the IANA zone across DST (27.362837ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (33.004841ms) +✔ two blocking decisions get two DMs with different nonces (21.768645ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.421146ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.006717ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (27.839774ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.77886ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (25.041516ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (161.21144ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (61.112773ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (20.296917ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.449409ms) +✔ an inbox read failure is logged and the next poll retries (0.869409ms) +✔ no Discord id reaches the journal or the log (10.32983ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.884583ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.513173ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.403405ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.464089ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.587224ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.591962ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.388675ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.545456ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.429635ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.477638ms) +✔ digest content stays within Discord's 2000 characters (0.340235ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.375699ms) +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 (372.337838ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (57.246794ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2297.274981ms) +✔ busExit and refuseInsideAgent (0.421121ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.030284ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.102798ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.552857ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.517368ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.089251ms) +✔ 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.925536ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.027423ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.33504ms) +✔ authorize: open channel, listed user (1.931272ms) +✔ authorize: wrong guild (0.189982ms) +✔ authorize: no guild (DM) (0.157779ms) +✔ authorize: unlisted channel (0.18683ms) +✔ authorize: unknown channel, no info (0.146678ms) +✔ authorize: thread of listed parent (0.197969ms) +✔ authorize: thread of unlisted parent (0.145916ms) +✔ authorize: text channel that is not a thread and not listed (0.122708ms) +✔ authorize: unlisted user (0.183533ms) +✔ authorize: no author (0.264893ms) +✔ authorize: bot author (listed id, bot flag) (0.259968ms) +✔ authorize: system author (0.14774ms) +✔ authorize: the bot itself (0.116946ms) +✔ authorize: webhook (0.106236ms) +✔ authorize: mention channel without mention (0.150081ms) +✔ authorize: mention channel with bot mention (0.176439ms) +✔ authorize: mention channel with @everyone only (0.499201ms) +✔ authorize: mention channel mentioning someone else (0.110156ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.122991ms) +✔ authorize: private thread under mention channel, mentioned (0.3239ms) +✔ authorize: private thread under mention channel, not mentioned (0.096298ms) +✔ authorize: thread in another guild per channel info (0.075588ms) +✔ authorize: not an object (0.062351ms) +✔ authorize: no id (0.06053ms) +✔ authorize: oversize content is accepted and flagged (0.073567ms) +✔ authorize: exactly the limit is not oversize (0.058582ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.485945ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.152046ms) +✔ binding: a complete binding validates and is frozen (3.425684ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.141945ms) +✔ binding: empty allowlists refuse (0.590751ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.751669ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.988316ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.730047ms) +✔ binding: file must be 0600, regular, not a symlink (1.795508ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.241415ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (116.502152ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.661386ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (422.471569ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (214.927913ms) +✔ cli: run refuses when STOP is present, before any network use (150.721805ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.951144ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.570816ms) +✔ 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.646576ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.812594ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.558125ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.177876ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.170751ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.6356ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.578277ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.157117ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.54556ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.128202ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.80801ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (34.412926ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.548021ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.511124ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.63614ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.130849ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.028945ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.862027ms) +✔ 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.820621ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.905224ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.344559ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.159632ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.504756ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.422853ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.883512ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.965183ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.334097ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.562132ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.225122ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.667398ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.600949ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.441715ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (63.624535ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (57.05796ms) +✔ engine: one prompt, one turn, text and usage come back (61.662112ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (366.995448ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.440028ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.064014ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (231.265489ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (127.256069ms) +✔ 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.85529ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.568922ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.335897ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.525471ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.342611ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.113013ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.5138ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.318652ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.510866ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.491385ms) +✔ gateway: op 9 resumable resumes (0.353346ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.836841ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.54704ms) +✔ gateway: close() is final and unparseable frames are ignored (0.61774ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.467916ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (77.217847ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.680187ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.483544ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (99.008648ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (90.548843ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (92.799709ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (229.565788ms) +✔ git: push pushes the named branch only and reports up to date (92.029415ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (97.074074ms) +✔ git: the credential helper answers get over https from a private file and nothing else (204.321635ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (743.152909ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.713142ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (96.468494ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.607066ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.301534ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (70.262641ms) +✔ 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 (336.611061ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.39441ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.448638ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.485653ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (49.85054ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (154.040521ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (103.798693ms) +✔ notices: a kind is recorded per UTC day and found again (0.644128ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.043981ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.403883ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.16611ms) +✔ 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.108012ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (61.196009ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (46.242697ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (75.100233ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (699.071346ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.419085ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.005535ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.613088ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.674523ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.041611ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.097134ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.337198ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.203316ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.903674ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.819093ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.05144ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.018042ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.178702ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.773509ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.541534ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.06891ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.427365ms) +✔ 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.948754ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.180273ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199006ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.337322ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.560483ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.837649ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.925993ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.759524ms) +✔ tools: listing and search caps hold (10.773015ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.929124ms) +✔ 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.225338ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.293259ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.213271ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.591889ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.975545ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.905601ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.360302ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.263509ms) +✔ 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.728519ms) +✔ 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.167855ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.226639ms) +✔ 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.576987ms) +✔ 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.185515ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 22115.416853 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:301:1 +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (138.303819ms) + Error: write EPIPE + at epipe (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:113:24) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:307:71 + at ChildProcess.send (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:94:23) + at Object.close (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/host.mjs:184:36) + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:310:27) + at process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + code: 'EPIPE', + errno: -32, + syscall: 'write' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-G188.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-G188.txt new file mode 100644 index 00000000..b35010a6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-G188.txt @@ -0,0 +1,272 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (27.725004ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.861808ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (28.564691ms) +✔ decide prints a declining choice as declining (25.178783ms) +✔ an unknown outcome is reported once and never resent (15.039452ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.756226ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (19.747332ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.956081ms) +✔ every human command refuses inside an agent run before it touches the bus (21.351565ms) +✔ usage errors exit 4; no business and no host is a usage error (18.257531ms) +✔ agents and tasks print through the broker (14.04227ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.094164ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (73.122818ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (55.622341ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (63.195833ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (56.787227ms) +✔ a business without tracker.baseUrl gets no trackers entry (56.402734ms) +✔ an unknown business and a broken system config refuse with exit 3 (89.866092ms) +✔ empty views say so (0.866121ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.127362ms) +✔ tasks print the tracker fields the snapshot carries (0.255718ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (967.30061ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (150.187073ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (78.707426ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (127.111373ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (129.814801ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (78.327248ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (133.345574ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.733107ms) +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (133.706712ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.956798ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.478231ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (204.490025ms) +✔ bus start refuses with exit 3 without a notifier config (91.936624ms) +✔ bus start runs until bus stop; status reports it while it runs (599.011152ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.458927ms) +✔ zoned uses the IANA zone across DST (25.33944ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (35.749269ms) +✔ two blocking decisions get two DMs with different nonces (24.558137ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (1.37865ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (19.234281ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (34.395104ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.592443ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (19.345267ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (142.375961ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (59.048147ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.977223ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.041438ms) +✔ an inbox read failure is logged and the next poll retries (0.798456ms) +✔ no Discord id reaches the journal or the log (10.086742ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.456131ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.563806ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.348498ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.498688ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.574324ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.095986ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.376607ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.564945ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.436412ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.4563ms) +✔ digest content stays within Discord's 2000 characters (0.417583ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.049151ms) +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 (372.651182ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (62.998163ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2274.624121ms) +✔ busExit and refuseInsideAgent (3.612584ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.003148ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.443646ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.543017ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.584522ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.38783ms) +✔ 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.810789ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.60538ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.270888ms) +✔ authorize: open channel, listed user (1.81043ms) +✔ authorize: wrong guild (0.186899ms) +✔ authorize: no guild (DM) (0.179106ms) +✔ authorize: unlisted channel (0.198354ms) +✔ authorize: unknown channel, no info (0.153258ms) +✔ authorize: thread of listed parent (0.195673ms) +✔ authorize: thread of unlisted parent (0.1863ms) +✔ authorize: text channel that is not a thread and not listed (0.125952ms) +✔ authorize: unlisted user (0.257968ms) +✔ authorize: no author (0.253408ms) +✔ authorize: bot author (listed id, bot flag) (0.910052ms) +✔ authorize: system author (0.226458ms) +✔ authorize: the bot itself (0.090903ms) +✔ authorize: webhook (0.086991ms) +✔ authorize: mention channel without mention (0.119171ms) +✔ authorize: mention channel with bot mention (0.140139ms) +✔ authorize: mention channel with @everyone only (0.104111ms) +✔ authorize: mention channel mentioning someone else (0.088684ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.124394ms) +✔ authorize: private thread under mention channel, mentioned (0.100233ms) +✔ authorize: private thread under mention channel, not mentioned (0.072504ms) +✔ authorize: thread in another guild per channel info (0.091162ms) +✔ authorize: not an object (0.06125ms) +✔ authorize: no id (0.059814ms) +✔ authorize: oversize content is accepted and flagged (0.061261ms) +✔ authorize: exactly the limit is not oversize (0.063628ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.461563ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.141433ms) +✔ binding: a complete binding validates and is frozen (2.477734ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.305401ms) +✔ binding: empty allowlists refuse (0.329257ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.110092ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.344477ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.450987ms) +✔ binding: file must be 0600, regular, not a symlink (1.523266ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.334094ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (115.303312ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.802188ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (391.525902ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (180.216922ms) +✔ cli: run refuses when STOP is present, before any network use (138.675771ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.874097ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.095084ms) +✔ 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.792175ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.836111ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.616706ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.202123ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.529595ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.5809ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.199597ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.930766ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.210198ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.108887ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.637519ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.43494ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.552117ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.27629ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.204142ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.167342ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.837091ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.494918ms) +✔ 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.722664ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.707717ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.258541ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.610759ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.113588ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.848003ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.764722ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.860669ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.164086ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.391768ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.456197ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.982474ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.511074ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.405558ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (60.224358ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (51.874251ms) +✔ engine: one prompt, one turn, text and usage come back (54.732804ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (370.960466ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (229.920416ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.197407ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.740568ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.697407ms) +✔ 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.476669ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.00822ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.349876ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.482971ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.004547ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.437217ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (48.025521ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.418776ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.587165ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.958786ms) +✔ gateway: op 9 resumable resumes (0.316817ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.896081ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.53155ms) +✔ gateway: close() is final and unparseable frames are ignored (0.719708ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (72.577945ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (64.073997ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (136.680182ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.466574ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (98.52451ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (89.332896ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (83.826305ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (198.687787ms) +✔ git: push pushes the named branch only and reports up to date (70.744788ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (80.054235ms) +✔ git: the credential helper answers get over https from a private file and nothing else (207.681334ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (758.537557ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.8765ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (100.147953ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.902376ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (6.615975ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (56.645221ms) +✔ 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 (329.179783ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.39827ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.821806ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.165319ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (46.28033ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (141.204747ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (82.427663ms) +✔ notices: a kind is recorded per UTC day and found again (0.411306ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.321794ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.193377ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.956844ms) +✔ 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.026651ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (51.93831ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (58.71983ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (77.918761ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (639.543651ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.402223ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.070935ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.621755ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.720534ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.812191ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (8.506332ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.353446ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.566329ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.108639ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.257144ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.800698ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.090073ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.561691ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.673874ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.623866ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.500646ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.424022ms) +✔ 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.004408ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.212984ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.226511ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.263028ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.407763ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.480263ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.143727ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.402021ms) +✔ tools: listing and search caps hold (8.754398ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.858684ms) +✔ 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.786359ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.46187ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.253895ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.957183ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.82702ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.767986ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.611241ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.410561ms) +✔ 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.827543ms) +✔ 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.196143ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.235084ms) +✔ 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.83305ms) +✔ 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.244223ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 22074.480532 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:301:1 +✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (133.706712ms) + Error: write EPIPE + at epipe (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:113:24) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:307:71 + at ChildProcess.send (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:94:23) + at Object.close (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/host.mjs:188:34) + 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:310:16) + at async Test.run (node:internal/test_runner/test:1409:7) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + code: 'EPIPE', + errno: -32, + syscall: 'write' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-J4a.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-J4a.txt new file mode 100644 index 00000000..67b48861 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-J4a.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (22.726199ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.099193ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (18.669567ms) +✔ decide prints a declining choice as declining (25.574997ms) +✔ an unknown outcome is reported once and never resent (16.609845ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (18.22855ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (24.848441ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.341316ms) +✔ every human command refuses inside an agent run before it touches the bus (31.557999ms) +✔ usage errors exit 4; no business and no host is a usage error (21.149079ms) +✔ agents and tasks print through the broker (21.441955ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.723751ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (67.432148ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (60.256213ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (61.543467ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (57.032146ms) +✔ a business without tracker.baseUrl gets no trackers entry (68.288178ms) +✔ an unknown business and a broken system config refuse with exit 3 (84.188563ms) +✔ empty views say so (0.981318ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.374963ms) +✔ tasks print the tracker fields the snapshot carries (0.344917ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (978.255238ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (138.645401ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (72.426735ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (125.339275ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (120.370682ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (68.565388ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (121.631302ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.252395ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (133.43766ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (28.224116ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.350116ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (197.47672ms) +✔ bus start refuses with exit 3 without a notifier config (85.754313ms) +✔ bus start runs until bus stop; status reports it while it runs (594.820116ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.981795ms) +✔ zoned uses the IANA zone across DST (31.364199ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (25.098743ms) +✔ two blocking decisions get two DMs with different nonces (24.076035ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.355916ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.682652ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (26.736546ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.784885ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (22.541675ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (158.299538ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (56.517676ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.572217ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.163424ms) +✔ an inbox read failure is logged and the next poll retries (0.713737ms) +✔ no Discord id reaches the journal or the log (12.146805ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.384862ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.726507ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.314435ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.671033ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.648588ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.368848ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.426037ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.586933ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.403667ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.50309ms) +✔ digest content stays within Discord's 2000 characters (0.284395ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.786515ms) +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 (377.492026ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (73.721744ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2284.728472ms) +✔ busExit and refuseInsideAgent (0.411573ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.119289ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.484911ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.557944ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.490382ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.016332ms) +✔ 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.648142ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.801007ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.466221ms) +✔ authorize: open channel, listed user (1.495214ms) +✔ authorize: wrong guild (0.184132ms) +✔ authorize: no guild (DM) (0.158573ms) +✔ authorize: unlisted channel (0.187997ms) +✔ authorize: unknown channel, no info (0.172406ms) +✔ authorize: thread of listed parent (0.206847ms) +✔ authorize: thread of unlisted parent (0.136909ms) +✔ authorize: text channel that is not a thread and not listed (0.135584ms) +✔ authorize: unlisted user (0.182579ms) +✔ authorize: no author (0.259783ms) +✔ authorize: bot author (listed id, bot flag) (0.151355ms) +✔ authorize: system author (0.13244ms) +✔ authorize: the bot itself (0.09681ms) +✔ authorize: webhook (0.124532ms) +✔ authorize: mention channel without mention (0.180748ms) +✔ authorize: mention channel with bot mention (3.388141ms) +✔ authorize: mention channel with @everyone only (0.246928ms) +✔ authorize: mention channel mentioning someone else (0.100541ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.091404ms) +✔ authorize: private thread under mention channel, mentioned (0.106613ms) +✔ authorize: private thread under mention channel, not mentioned (0.077901ms) +✔ authorize: thread in another guild per channel info (0.075693ms) +✔ authorize: not an object (0.077901ms) +✔ authorize: no id (0.069592ms) +✔ authorize: oversize content is accepted and flagged (0.074525ms) +✔ authorize: exactly the limit is not oversize (0.068144ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.544098ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.174489ms) +✔ binding: a complete binding validates and is frozen (2.666765ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.251329ms) +✔ binding: empty allowlists refuse (0.367955ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.247392ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.852648ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.231469ms) +✔ binding: file must be 0600, regular, not a symlink (1.680945ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.274394ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (108.009805ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.019763ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (408.305407ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (198.458301ms) +✔ cli: run refuses when STOP is present, before any network use (119.546867ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.185243ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.101053ms) +✔ 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.666651ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.91693ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.482484ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.16619ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.390299ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.838456ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.60475ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.143404ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.880149ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.824679ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.245108ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.364214ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.800024ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (6.313398ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.949918ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (9.378475ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.77505ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.826286ms) +✔ 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.029445ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.002168ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.599938ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.425683ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (7.105805ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.39627ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.730082ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.790504ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.175493ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.460952ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.103831ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.771749ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.603922ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.431817ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (50.800684ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (59.155256ms) +✔ engine: one prompt, one turn, text and usage come back (56.618599ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (351.382076ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.187205ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (112.804001ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (226.221691ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.729691ms) +✔ 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.9388ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.299749ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.274359ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.955363ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (429.833023ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.641738ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (43.387829ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.294965ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.623289ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.473724ms) +✔ gateway: op 9 resumable resumes (0.338716ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.839054ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.486022ms) +✔ gateway: close() is final and unparseable frames are ignored (0.50621ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (68.972907ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (67.428241ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (154.569672ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.470823ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (96.889348ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (89.306211ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (90.186108ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (198.413848ms) +✔ git: push pushes the named branch only and reports up to date (62.187887ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (75.702707ms) +✔ git: the credential helper answers get over https from a private file and nothing else (188.888624ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (731.68452ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.796711ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (97.691222ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.596545ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.860128ms) +✔ 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.069801ms) +✔ 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 (336.680712ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.409477ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.121286ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.945829ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (35.735586ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (142.138184ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (80.656688ms) +✔ notices: a kind is recorded per UTC day and found again (0.408453ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.32331ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.75518ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.184563ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (83.350178ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (55.261995ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.622915ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (71.811221ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (627.650094ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (4.382351ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.196873ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.656126ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.737402ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.79421ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.166104ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.553735ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.499601ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (5.618046ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (33.45824ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.305094ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.194548ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.053531ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.793119ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.206796ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.545737ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.432412ms) +✔ 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.039886ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.194603ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199809ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.23459ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (5.436958ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.085675ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.861864ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.887542ms) +✔ tools: listing and search caps hold (14.009015ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.219245ms) +✔ 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.909847ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.444722ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.502471ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.795596ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.316308ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.824395ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.516548ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.433468ms) +✔ 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.75181ms) +✔ 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.250146ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.228631ms) +✔ 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.02676ms) +✔ 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.286185ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3104.897055 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:332:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.368848ms) + 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:361: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/r2/mut-J4b.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-J4b.txt new file mode 100644 index 00000000..a95d4766 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-J4b.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (24.158742ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (36.553469ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (18.076978ms) +✔ decide prints a declining choice as declining (24.162789ms) +✔ an unknown outcome is reported once and never resent (15.341305ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.762754ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.842692ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.674793ms) +✔ every human command refuses inside an agent run before it touches the bus (17.1648ms) +✔ usage errors exit 4; no business and no host is a usage error (20.246485ms) +✔ agents and tasks print through the broker (22.345053ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.083256ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (69.210037ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (55.212696ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (55.68185ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (57.395285ms) +✔ a business without tracker.baseUrl gets no trackers entry (66.335789ms) +✔ an unknown business and a broken system config refuse with exit 3 (81.547046ms) +✔ empty views say so (1.154157ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.286046ms) +✔ tasks print the tracker fields the snapshot carries (0.200556ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (963.804965ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (144.952239ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (78.042193ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (122.383088ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (120.271078ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.289915ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (118.88565ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.403813ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (135.474054ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.19171ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.699633ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (203.124317ms) +✔ bus start refuses with exit 3 without a notifier config (88.133209ms) +✔ bus start runs until bus stop; status reports it while it runs (602.377329ms) +✔ bus-service.sh renders the unit and installs it into a given directory (28.269352ms) +✔ zoned uses the IANA zone across DST (24.967725ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (24.332286ms) +✔ two blocking decisions get two DMs with different nonces (19.766757ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.35656ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (22.417887ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.46273ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (27.250478ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.451332ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (146.832363ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (68.028414ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.778487ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.677167ms) +✔ an inbox read failure is logged and the next poll retries (0.706508ms) +✔ no Discord id reaches the journal or the log (9.774456ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.082679ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.926156ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.240621ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.50467ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.591536ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.443028ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.364006ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.513258ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.383339ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.581415ms) +✔ digest content stays within Discord's 2000 characters (0.290206ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.30446ms) +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 (359.572088ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (53.589497ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2257.255025ms) +✔ busExit and refuseInsideAgent (0.469498ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.123227ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.639687ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.555273ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.465523ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.86442ms) +✔ 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.901415ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.9026ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.03818ms) +✔ authorize: open channel, listed user (1.737541ms) +✔ authorize: wrong guild (0.192858ms) +✔ authorize: no guild (DM) (0.271658ms) +✔ authorize: unlisted channel (0.190478ms) +✔ authorize: unknown channel, no info (0.17056ms) +✔ authorize: thread of listed parent (0.196923ms) +✔ authorize: thread of unlisted parent (0.145197ms) +✔ authorize: text channel that is not a thread and not listed (0.130481ms) +✔ authorize: unlisted user (0.851026ms) +✔ authorize: no author (0.279706ms) +✔ authorize: bot author (listed id, bot flag) (0.133807ms) +✔ authorize: system author (0.118535ms) +✔ authorize: the bot itself (0.073182ms) +✔ authorize: webhook (0.083921ms) +✔ authorize: mention channel without mention (0.125346ms) +✔ authorize: mention channel with bot mention (0.707573ms) +✔ authorize: mention channel with @everyone only (0.175719ms) +✔ authorize: mention channel mentioning someone else (0.167736ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.096688ms) +✔ authorize: private thread under mention channel, mentioned (0.090619ms) +✔ authorize: private thread under mention channel, not mentioned (0.07697ms) +✔ authorize: thread in another guild per channel info (0.075341ms) +✔ authorize: not an object (0.067585ms) +✔ authorize: no id (0.061902ms) +✔ authorize: oversize content is accepted and flagged (0.068441ms) +✔ authorize: exactly the limit is not oversize (0.061633ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.442616ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.149945ms) +✔ binding: a complete binding validates and is frozen (2.726218ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.352258ms) +✔ binding: empty allowlists refuse (0.383053ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.288902ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.280412ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.4798ms) +✔ binding: file must be 0600, regular, not a symlink (1.927939ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.551198ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (109.193081ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.05798ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (391.396888ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (184.213524ms) +✔ cli: run refuses when STOP is present, before any network use (146.290668ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.149828ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.245039ms) +✔ 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.063291ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.092513ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.651459ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.204628ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.470906ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.732986ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.235227ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.081434ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.190745ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.666723ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.90144ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.083308ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (9.88305ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.109129ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (5.294589ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.460322ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.066995ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.987533ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (9.087361ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.278507ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.432537ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.228616ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.262337ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.639558ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.86637ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.510851ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.195851ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.346978ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.187305ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.643748ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.594534ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.408496ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (52.577893ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (51.071115ms) +✔ engine: one prompt, one turn, text and usage come back (55.303446ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (365.328274ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.660705ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.173269ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (227.480275ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.243511ms) +✔ 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.363735ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.192422ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.329328ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.493796ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.018107ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.134374ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.391758ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (1.992776ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.647812ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.477697ms) +✔ gateway: op 9 resumable resumes (0.336546ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.767568ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.604046ms) +✔ gateway: close() is final and unparseable frames are ignored (0.560699ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (66.767761ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (65.891655ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.261181ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.34243ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (88.725305ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (78.848385ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (86.752223ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (193.898386ms) +✔ git: push pushes the named branch only and reports up to date (73.617059ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (81.155433ms) +✔ git: the credential helper answers get over https from a private file and nothing else (197.350031ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (737.929872ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.809675ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (94.882871ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.687739ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.687967ms) +✔ 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.848385ms) +✔ 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 (322.078843ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.448333ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.107123ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.919341ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (36.127689ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (128.744857ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (87.145498ms) +✔ notices: a kind is recorded per UTC day and found again (0.409792ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.572224ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.508155ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.428227ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (56.614624ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (54.082125ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (44.075075ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (72.766226ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (637.796866ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.465114ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.732941ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.560541ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.717624ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.851678ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (10.954585ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.150362ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.683606ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.174145ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.628685ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (10.957921ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.512049ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.738692ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.659027ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.49639ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.739149ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.445536ms) +✔ 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.003404ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.284995ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.204298ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.291654ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.018174ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.054626ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.134532ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (5.138343ms) +✔ tools: listing and search caps hold (13.624938ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.974199ms) +✔ 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.73413ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.163623ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (2.061201ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.958608ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.387374ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.633358ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.722956ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.477962ms) +✔ 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.538954ms) +✔ 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.499409ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.215807ms) +✔ 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.858386ms) +✔ 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.214932ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3100.5543 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:332:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (1.443028ms) + 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:361: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/r2/mut-J4c.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-J4c.txt new file mode 100644 index 00000000..72fe32da --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-J4c.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (37.422929ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (37.071594ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.080584ms) +✔ decide prints a declining choice as declining (20.04342ms) +✔ an unknown outcome is reported once and never resent (14.804159ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (22.098609ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.347239ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (18.919893ms) +✔ every human command refuses inside an agent run before it touches the bus (17.236435ms) +✔ usage errors exit 4; no business and no host is a usage error (17.689522ms) +✔ agents and tasks print through the broker (15.65574ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.214317ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (82.349948ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.323357ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (61.117009ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (69.683038ms) +✔ a business without tracker.baseUrl gets no trackers entry (54.893936ms) +✔ an unknown business and a broken system config refuse with exit 3 (86.724411ms) +✔ empty views say so (1.085073ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.382591ms) +✔ tasks print the tracker fields the snapshot carries (0.23399ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (970.508565ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (147.889727ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (73.9945ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (120.130908ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (120.81105ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (70.561705ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (139.814459ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.086439ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (136.714994ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.234726ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.280724ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (201.883187ms) +✔ bus start refuses with exit 3 without a notifier config (92.117037ms) +✔ bus start runs until bus stop; status reports it while it runs (601.515435ms) +✔ bus-service.sh renders the unit and installs it into a given directory (28.052979ms) +✔ zoned uses the IANA zone across DST (26.821343ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (36.678724ms) +✔ two blocking decisions get two DMs with different nonces (27.439441ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.358875ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.631759ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (26.362556ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (31.38823ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (25.761153ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (148.316194ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (60.872804ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.323618ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.754695ms) +✔ an inbox read failure is logged and the next poll retries (0.769083ms) +✔ no Discord id reaches the journal or the log (10.734013ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.221893ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.613008ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.698013ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.634529ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.608086ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (2.587832ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.407309ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.675645ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.433644ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.475756ms) +✔ digest content stays within Discord's 2000 characters (0.282334ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.633409ms) +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 (405.859855ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (61.851914ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2262.673488ms) +✔ busExit and refuseInsideAgent (0.429279ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.418979ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.936612ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.5768ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.530584ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.192158ms) +✔ 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.118732ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.146959ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.561858ms) +✔ authorize: open channel, listed user (1.809982ms) +✔ authorize: wrong guild (0.194538ms) +✔ authorize: no guild (DM) (0.178467ms) +✔ authorize: unlisted channel (0.181147ms) +✔ authorize: unknown channel, no info (0.329193ms) +✔ authorize: thread of listed parent (0.193682ms) +✔ authorize: thread of unlisted parent (0.159838ms) +✔ authorize: text channel that is not a thread and not listed (0.144147ms) +✔ authorize: unlisted user (0.199061ms) +✔ authorize: no author (0.543357ms) +✔ authorize: bot author (listed id, bot flag) (0.255464ms) +✔ authorize: system author (0.131552ms) +✔ authorize: the bot itself (0.101056ms) +✔ authorize: webhook (0.098724ms) +✔ authorize: mention channel without mention (0.137969ms) +✔ authorize: mention channel with bot mention (0.13531ms) +✔ authorize: mention channel with @everyone only (0.163329ms) +✔ authorize: mention channel mentioning someone else (0.086988ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.08537ms) +✔ authorize: private thread under mention channel, mentioned (0.084608ms) +✔ authorize: private thread under mention channel, not mentioned (0.09232ms) +✔ authorize: thread in another guild per channel info (0.086332ms) +✔ authorize: not an object (0.069381ms) +✔ authorize: no id (0.080801ms) +✔ authorize: oversize content is accepted and flagged (0.077372ms) +✔ authorize: exactly the limit is not oversize (0.073449ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.485697ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.154697ms) +✔ binding: a complete binding validates and is frozen (3.103639ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.886486ms) +✔ binding: empty allowlists refuse (0.455986ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.524388ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.698656ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.462978ms) +✔ binding: file must be 0600, regular, not a symlink (1.364085ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.505284ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (115.667508ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.155697ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (399.506418ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (182.648547ms) +✔ cli: run refuses when STOP is present, before any network use (132.281894ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.772792ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.187514ms) +✔ 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.853958ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (22.563378ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.520334ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.344843ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.665819ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (4.529538ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.448457ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.821618ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.227141ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.444179ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.244218ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.377918ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (12.104754ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.519883ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.140638ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.799755ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.643622ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.732274ms) +✔ 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.684933ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.521755ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.067725ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.448702ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.310496ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.566141ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.932251ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.881546ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.439167ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.427123ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (3.182141ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (2.184927ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.726063ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.412464ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (57.032018ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (53.417567ms) +✔ engine: one prompt, one turn, text and usage come back (47.006518ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (360.320218ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (229.716776ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (114.522026ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (225.499766ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (126.961635ms) +✔ 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.478207ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.178135ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.428943ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.543708ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.549809ms) +✔ engine: a malformed JSONL line fails the turn, not the process (28.379874ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.340845ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.649166ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.623281ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.548057ms) +✔ gateway: op 9 resumable resumes (0.34792ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.935919ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.567601ms) +✔ gateway: close() is final and unparseable frames are ignored (0.463194ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (67.664274ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (68.192463ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (131.318957ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.461261ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (98.992052ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (86.410784ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.56538ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (191.505094ms) +✔ git: push pushes the named branch only and reports up to date (68.717608ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (84.934ms) +✔ git: the credential helper answers get over https from a private file and nothing else (193.056389ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (744.784228ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.916805ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (92.126751ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.22946ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.505797ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (69.861407ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (342.848681ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.445998ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (21.897438ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.982212ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (41.258927ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (127.366567ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (89.381422ms) +✔ notices: a kind is recorded per UTC day and found again (0.695283ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.377191ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (4.412125ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.892844ms) +✔ 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.118544ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (54.352578ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (45.339287ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (86.40136ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (641.518432ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.520334ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (9.428935ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.259518ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (3.51936ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.871197ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.934274ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.090368ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.636415ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.098729ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.439635ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (15.747362ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.69182ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.387431ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.80604ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.718797ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.014195ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.426891ms) +✔ 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.968259ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.28499ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.208434ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.371375ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.890958ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.670054ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (4.777472ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.413435ms) +✔ tools: listing and search caps hold (10.026553ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.642266ms) +✔ 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.26989ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.340782ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.329359ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.961826ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (4.569406ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.056576ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.951236ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.425029ms) +✔ 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.295405ms) +✔ 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.111749ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.233388ms) +✔ 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.825682ms) +✔ 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.191929ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3159.188008 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:332:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (2.587832ms) + 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:361: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/r2/mut-J4d.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-J4d.txt new file mode 100644 index 00000000..675ccf0e --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-J4d.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (34.604208ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.054035ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.764354ms) +✔ decide prints a declining choice as declining (18.885385ms) +✔ an unknown outcome is reported once and never resent (12.460984ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.301957ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (14.624948ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.11494ms) +✔ every human command refuses inside an agent run before it touches the bus (13.866522ms) +✔ usage errors exit 4; no business and no host is a usage error (28.735889ms) +✔ agents and tasks print through the broker (26.540393ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.085676ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (76.076506ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (57.037896ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (63.969567ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (66.097615ms) +✔ a business without tracker.baseUrl gets no trackers entry (72.570193ms) +✔ an unknown business and a broken system config refuse with exit 3 (82.532869ms) +✔ empty views say so (1.224819ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (2.068407ms) +✔ tasks print the tracker fields the snapshot carries (0.254337ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (981.60008ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (157.257907ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (86.390523ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (132.002306ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (126.289403ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (81.164738ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (136.059927ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (69.451464ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (132.250025ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.799123ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.306365ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (210.88687ms) +✔ bus start refuses with exit 3 without a notifier config (86.041721ms) +✔ bus start runs until bus stop; status reports it while it runs (596.152761ms) +✔ bus-service.sh renders the unit and installs it into a given directory (28.251643ms) +✔ zoned uses the IANA zone across DST (30.087786ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (45.403788ms) +✔ two blocking decisions get two DMs with different nonces (26.393587ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.466722ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (20.101786ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (22.273066ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (21.487198ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (21.779764ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (149.889362ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (66.436359ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.96293ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.987668ms) +✔ an inbox read failure is logged and the next poll retries (0.761583ms) +✔ no Discord id reaches the journal or the log (10.380825ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.978632ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.105246ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.34651ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.511658ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.651439ms) +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (2.210922ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.401803ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.517836ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.467409ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.481863ms) +✔ digest content stays within Discord's 2000 characters (0.290913ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.78117ms) +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 (404.261782ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (72.775506ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2268.694712ms) +✔ busExit and refuseInsideAgent (3.5789ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.540198ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.725294ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.635284ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.491455ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (20.224505ms) +✔ 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.8625ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.525788ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.473542ms) +✔ authorize: open channel, listed user (1.820629ms) +✔ authorize: wrong guild (0.202127ms) +✔ authorize: no guild (DM) (0.168119ms) +✔ authorize: unlisted channel (0.222404ms) +✔ authorize: unknown channel, no info (0.174177ms) +✔ authorize: thread of listed parent (0.196227ms) +✔ authorize: thread of unlisted parent (0.183736ms) +✔ authorize: text channel that is not a thread and not listed (0.155604ms) +✔ authorize: unlisted user (0.213546ms) +✔ authorize: no author (0.942744ms) +✔ authorize: bot author (listed id, bot flag) (0.151721ms) +✔ authorize: system author (0.115806ms) +✔ authorize: the bot itself (0.076729ms) +✔ authorize: webhook (0.094283ms) +✔ authorize: mention channel without mention (0.218164ms) +✔ authorize: mention channel with bot mention (0.170093ms) +✔ authorize: mention channel with @everyone only (0.097306ms) +✔ authorize: mention channel mentioning someone else (0.075246ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.13087ms) +✔ authorize: private thread under mention channel, mentioned (0.105726ms) +✔ authorize: private thread under mention channel, not mentioned (0.082724ms) +✔ authorize: thread in another guild per channel info (0.084219ms) +✔ authorize: not an object (0.064336ms) +✔ authorize: no id (0.069861ms) +✔ authorize: oversize content is accepted and flagged (0.386853ms) +✔ authorize: exactly the limit is not oversize (0.071995ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.426175ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.147486ms) +✔ binding: a complete binding validates and is frozen (2.480336ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.28277ms) +✔ binding: empty allowlists refuse (0.391807ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.343858ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.091459ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.496759ms) +✔ binding: file must be 0600, regular, not a symlink (2.598319ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.473508ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (121.074688ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.12547ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (460.408864ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (201.545322ms) +✔ cli: run refuses when STOP is present, before any network use (140.954891ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.972347ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.18554ms) +✔ 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.895686ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.94536ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.709978ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.452756ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.704967ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.226804ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.267094ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.551428ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.093712ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.285814ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.717728ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.575061ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.80267ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (8.43569ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (8.428094ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (10.332193ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (7.290513ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.968591ms) +✔ receipt: an admitted message gets one eyes reaction on the inbound message; drops and refusals get none; a failed reaction is recorded and does not fail the turn (9.896868ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.438948ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.944514ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.264162ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.860405ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.50394ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.781343ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.848896ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.161653ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.496068ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.448818ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.866369ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.773121ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.485371ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (55.57274ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (51.366865ms) +✔ engine: one prompt, one turn, text and usage come back (49.824237ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (383.049001ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (255.898307ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.220681ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.931619ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.909235ms) +✔ 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.316315ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.239112ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.391394ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.516072ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.235616ms) +✔ engine: a malformed JSONL line fails the turn, not the process (27.385271ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.991947ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.440667ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.602604ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.524766ms) +✔ gateway: op 9 resumable resumes (0.301042ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.831424ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.573224ms) +✔ gateway: close() is final and unparseable frames are ignored (0.510912ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (75.714083ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (72.684299ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (152.536589ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.445177ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (86.357762ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (139.426177ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (96.914383ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (204.70006ms) +✔ git: push pushes the named branch only and reports up to date (72.011335ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (95.687033ms) +✔ git: the credential helper answers get over https from a private file and nothing else (213.624448ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (770.360271ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (6.599884ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (88.898613ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.372596ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (11.477013ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (86.475051ms) +✔ 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 (381.975631ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.440673ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (24.912468ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.058676ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.512903ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (148.189775ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (94.252275ms) +✔ notices: a kind is recorded per UTC day and found again (0.432961ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.87512ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.358779ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.505854ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (71.220699ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (48.053437ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (40.728666ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (86.81428ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (719.30105ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.451802ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (6.109889ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.694139ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.755482ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.392521ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (8.251677ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (5.351041ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.934493ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (8.265875ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (45.998311ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.958637ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.131065ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.483135ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.545878ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.431714ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1010.812178ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.44209ms) +✔ 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.052799ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.249708ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.205669ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.43342ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.517176ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.670294ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.015399ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.081116ms) +✔ tools: listing and search caps hold (11.352476ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.960546ms) +✔ 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.540842ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.286092ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.754676ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.392339ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.992815ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.878643ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.88394ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.271061ms) +✔ 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.910195ms) +✔ 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.099399ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.216198ms) +✔ 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.522492ms) +✔ 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.213869ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3192.79447 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:332:1 +✖ the journal: a line with a wrong type refuses with exit 3 and names the field (2.210922ms) + 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:361: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/r2/mut-M28.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-M28.txt new file mode 100644 index 00000000..eb70a38e --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-M28.txt @@ -0,0 +1,281 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (34.46118ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (42.718341ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.250013ms) +✔ decide prints a declining choice as declining (22.384696ms) +✔ an unknown outcome is reported once and never resent (17.567777ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.172845ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.050554ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.2269ms) +✔ every human command refuses inside an agent run before it touches the bus (20.88717ms) +✔ usage errors exit 4; no business and no host is a usage error (21.971726ms) +✔ agents and tasks print through the broker (18.828716ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.666498ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (80.650649ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.900192ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (65.390021ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (59.739908ms) +✔ a business without tracker.baseUrl gets no trackers entry (65.909697ms) +✔ an unknown business and a broken system config refuse with exit 3 (105.349397ms) +✔ empty views say so (1.182027ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.557529ms) +✔ tasks print the tracker fields the snapshot carries (0.300621ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (998.628257ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (147.164935ms) +✖ a second host for the same data root refuses with exit 3 while the first runs (122.303686ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.107194ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (127.702424ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (72.658936ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (129.777837ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (85.684017ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (135.70576ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.4901ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.67338ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (204.802721ms) +✔ bus start refuses with exit 3 without a notifier config (84.46603ms) +✔ bus start runs until bus stop; status reports it while it runs (593.406958ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.651811ms) +✔ zoned uses the IANA zone across DST (36.475422ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (37.313587ms) +✔ two blocking decisions get two DMs with different nonces (30.440144ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.315972ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (26.519409ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (27.850237ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (31.924286ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (21.392784ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (159.653661ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (57.455517ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.40533ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (14.568724ms) +✔ an inbox read failure is logged and the next poll retries (1.069928ms) +✔ no Discord id reaches the journal or the log (10.98208ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.975297ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.591215ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.807701ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.559725ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.637835ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.080466ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.373419ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.515202ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.397955ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.557113ms) +✔ digest content stays within Discord's 2000 characters (0.29972ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.543221ms) +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 (394.176938ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (63.787445ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2296.025495ms) +✔ busExit and refuseInsideAgent (0.395528ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.279447ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (2.185478ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (1.004833ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.574398ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (25.971319ms) +✔ 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.048344ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.291885ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.194355ms) +✔ authorize: open channel, listed user (2.001367ms) +✔ authorize: wrong guild (0.186392ms) +✔ authorize: no guild (DM) (0.350043ms) +✔ authorize: unlisted channel (0.200012ms) +✔ authorize: unknown channel, no info (0.277042ms) +✔ authorize: thread of listed parent (0.230679ms) +✔ authorize: thread of unlisted parent (0.244729ms) +✔ authorize: text channel that is not a thread and not listed (0.11864ms) +✔ authorize: unlisted user (0.451057ms) +✔ authorize: no author (0.31611ms) +✔ authorize: bot author (listed id, bot flag) (0.164631ms) +✔ authorize: system author (0.132288ms) +✔ authorize: the bot itself (0.105121ms) +✔ authorize: webhook (0.107934ms) +✔ authorize: mention channel without mention (0.144878ms) +✔ authorize: mention channel with bot mention (0.14995ms) +✔ authorize: mention channel with @everyone only (0.154474ms) +✔ authorize: mention channel mentioning someone else (0.083761ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.09336ms) +✔ authorize: private thread under mention channel, mentioned (0.088262ms) +✔ authorize: private thread under mention channel, not mentioned (0.078244ms) +✔ authorize: thread in another guild per channel info (0.078727ms) +✔ authorize: not an object (0.064903ms) +✔ authorize: no id (0.069133ms) +✔ authorize: oversize content is accepted and flagged (0.075616ms) +✔ authorize: exactly the limit is not oversize (0.07111ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.484811ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.15209ms) +✔ binding: a complete binding validates and is frozen (4.386481ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.332464ms) +✔ binding: empty allowlists refuse (0.384017ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.361972ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.611248ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.471599ms) +✔ binding: file must be 0600, regular, not a symlink (1.524219ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.491878ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (117.338765ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.868015ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (456.667007ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (227.116343ms) +✔ cli: run refuses when STOP is present, before any network use (134.077829ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.57958ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.033987ms) +✔ 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.250477ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.534602ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.431492ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.092744ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.22347ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.101801ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.797309ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.029288ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.201328ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (2.207303ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.644949ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.442696ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.276767ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (8.763162ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.871857ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.48732ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (5.637294ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.755381ms) +✔ 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.301092ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (6.07665ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.313828ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (5.279328ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.765377ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.645274ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.8501ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.036411ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.431338ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.669957ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (2.7424ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (2.839234ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.693109ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.438378ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (71.171023ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (57.380647ms) +✔ engine: one prompt, one turn, text and usage come back (63.787084ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (353.115324ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (231.132813ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (106.436515ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.172827ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.11987ms) +✔ 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.654554ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.640007ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.333944ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.501024ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (432.123477ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.496899ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.848984ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.394024ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.830966ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.490513ms) +✔ gateway: op 9 resumable resumes (0.49192ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.884644ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.577702ms) +✔ gateway: close() is final and unparseable frames are ignored (0.421364ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (89.329351ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (86.724307ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (158.04282ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.389776ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (98.743587ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (97.876574ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (117.800032ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (206.476086ms) +✔ git: push pushes the named branch only and reports up to date (64.613298ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (85.682347ms) +✔ git: the credential helper answers get over https from a private file and nothing else (205.979349ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (742.056346ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.684078ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (85.196494ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (2.453684ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.724732ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (69.614151ms) +✔ 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 (412.764472ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.505546ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.408259ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.959767ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (45.763369ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (150.962069ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (82.927413ms) +✔ notices: a kind is recorded per UTC day and found again (0.499479ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.168334ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.529865ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.681754ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.558816ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (58.749216ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (60.119927ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (107.654965ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (705.801846ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.448901ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (4.496282ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.72985ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.817955ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.200875ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.755088ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.463876ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.681441ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.579708ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.73342ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.579184ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (10.366443ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (6.764733ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.934583ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.420706ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.873597ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.410526ms) +✔ 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.014759ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.22341ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.199103ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.362706ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.947093ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.013743ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.087367ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.862621ms) +✔ tools: listing and search caps hold (12.164265ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.991574ms) +✔ 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.601733ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.702669ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (2.210471ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (12.407443ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.805637ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.287762ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.500807ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.597459ms) +✔ 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.147564ms) +✔ 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.105647ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.217852ms) +✔ 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.501422ms) +✔ 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.148225ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3224.078743 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:191:1 +✖ a second host for the same data root refuses with exit 3 while the first runs (122.303686ms) + 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:201: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:201: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/r2/mut-N1a.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-N1a.txt new file mode 100644 index 00000000..dba1f973 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-N1a.txt @@ -0,0 +1,287 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (32.785143ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (39.613318ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.116322ms) +✔ decide prints a declining choice as declining (21.236149ms) +✔ an unknown outcome is reported once and never resent (20.2856ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (18.166577ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (14.736747ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.823682ms) +✔ every human command refuses inside an agent run before it touches the bus (17.283709ms) +✔ usage errors exit 4; no business and no host is a usage error (17.842765ms) +✔ agents and tasks print through the broker (20.70521ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (4.501727ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (72.271384ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.171466ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (55.05432ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (59.84891ms) +✔ a business without tracker.baseUrl gets no trackers entry (62.183024ms) +✔ an unknown business and a broken system config refuse with exit 3 (109.443955ms) +✔ empty views say so (1.941581ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.55407ms) +✔ tasks print the tracker fields the snapshot carries (0.284878ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (983.756857ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (143.331175ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.253795ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (133.526182ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (130.050972ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.790364ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (124.285574ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (72.8748ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.061279ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.595937ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.620001ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.595149ms) +✔ bus start refuses with exit 3 without a notifier config (84.673077ms) +✔ bus start runs until bus stop; status reports it while it runs (599.034968ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.385064ms) +✔ zoned uses the IANA zone across DST (29.32015ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (32.093104ms) +✔ two blocking decisions get two DMs with different nonces (24.339487ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.380926ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (21.641485ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.412687ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (34.384339ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (21.950533ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (147.176623ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (67.85557ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (17.354393ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.705565ms) +✔ an inbox read failure is logged and the next poll retries (1.292864ms) +✔ no Discord id reaches the journal or the log (13.724635ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.43761ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.635875ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.877784ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.988631ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.916525ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.689456ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.63676ms) +✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (1.002906ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.619733ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.766019ms) +✔ digest content stays within Discord's 2000 characters (0.414363ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.963111ms) +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.540416ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (78.133318ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2261.886579ms) +✔ busExit and refuseInsideAgent (0.404975ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.38687ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.650397ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.579376ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.436915ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.232529ms) +✔ 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.30509ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.549517ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.467827ms) +✔ authorize: open channel, listed user (2.12047ms) +✔ authorize: wrong guild (0.221031ms) +✔ authorize: no guild (DM) (0.320478ms) +✔ authorize: unlisted channel (0.245649ms) +✔ authorize: unknown channel, no info (0.246528ms) +✔ authorize: thread of listed parent (0.236648ms) +✔ authorize: thread of unlisted parent (0.172043ms) +✔ authorize: text channel that is not a thread and not listed (0.13838ms) +✔ authorize: unlisted user (0.222069ms) +✔ authorize: no author (0.295863ms) +✔ authorize: bot author (listed id, bot flag) (0.119327ms) +✔ authorize: system author (0.118228ms) +✔ authorize: the bot itself (0.081957ms) +✔ authorize: webhook (0.106363ms) +✔ authorize: mention channel without mention (0.141546ms) +✔ authorize: mention channel with bot mention (0.156272ms) +✔ authorize: mention channel with @everyone only (0.197264ms) +✔ authorize: mention channel mentioning someone else (0.090356ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.084168ms) +✔ authorize: private thread under mention channel, mentioned (0.088968ms) +✔ authorize: private thread under mention channel, not mentioned (0.067423ms) +✔ authorize: thread in another guild per channel info (0.081961ms) +✔ authorize: not an object (0.060095ms) +✔ authorize: no id (0.068223ms) +✔ authorize: oversize content is accepted and flagged (0.076584ms) +✔ authorize: exactly the limit is not oversize (0.073325ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.487053ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.14135ms) +✔ binding: a complete binding validates and is frozen (2.784197ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.381278ms) +✔ binding: empty allowlists refuse (0.314119ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.347398ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.702708ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.440487ms) +✔ binding: file must be 0600, regular, not a symlink (1.873649ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.674288ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (117.953952ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.82238ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (435.829321ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (194.715216ms) +✔ cli: run refuses when STOP is present, before any network use (139.446086ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.586151ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.722238ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (21.834744ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (22.671535ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.573802ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.415378ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.513706ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.998649ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.717283ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.221016ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.201271ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.315174ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.254552ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.185363ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.598939ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.368063ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.122311ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.679614ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.043552ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.788093ms) +✔ 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.640348ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.831719ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.489307ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.03311ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.200112ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.011081ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.926233ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.053884ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.282724ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.545135ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.218583ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.785159ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.403628ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.477039ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (62.576182ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (48.928465ms) +✔ engine: one prompt, one turn, text and usage come back (56.275209ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (369.007156ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (230.620366ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.775658ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.86874ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (131.936448ms) +✔ 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.379176ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.893284ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.354551ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.517091ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (427.462122ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.835689ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.920356ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.795155ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.042813ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.555113ms) +✔ gateway: op 9 resumable resumes (0.604983ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.887955ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.634997ms) +✔ gateway: close() is final and unparseable frames are ignored (0.506437ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (69.440858ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (59.404187ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (144.905989ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.469977ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (125.120538ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (92.662615ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (87.436344ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (207.118827ms) +✔ git: push pushes the named branch only and reports up to date (73.66507ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (98.868478ms) +✔ git: the credential helper answers get over https from a private file and nothing else (206.555736ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (760.903329ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.182619ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (96.044054ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.281212ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.740846ms) +✔ 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.70269ms) +✔ 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 (380.809018ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.433994ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.947937ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.934064ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (35.156902ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (144.969601ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (90.638653ms) +✔ notices: a kind is recorded per UTC day and found again (0.49102ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.69234ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.086177ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.85341ms) +✔ 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.7668ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (62.610912ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (52.244823ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (104.328488ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (685.874805ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.660008ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.537445ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.638554ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.831552ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.896568ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.811109ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.034323ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.601385ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.667596ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (33.318283ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (19.649509ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.774997ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.003522ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.814474ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.342017ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.948849ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.445227ms) +✔ 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.070159ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.241115ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.193684ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.278055ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.692239ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.624775ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.445555ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.19918ms) +✔ tools: listing and search caps hold (11.090696ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.152287ms) +✔ 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 (7.461367ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.556714ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (2.557182ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (7.360418ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (4.496653ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.333522ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.808146ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.519399ms) +✔ 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.014111ms) +✔ 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.207652ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.244652ms) +✔ 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.013093ms) +✔ 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.458801ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3158.269529 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:384:1 +✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (1.002906ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: ENOENT: no such file or directory, mkdir '/mnt/storage/scratch/tmp/mosaic-cli-td6Xqg/notify/demo' + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:389: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: Error: ENOENT: no such file or directory, mkdir '/mnt/storage/scratch/tmp/mosaic-cli-td6Xqg/notify/demo' + at mkdirSync (node:fs:1734:26) + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:162:5) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:389: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:389: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), + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-N1b.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-N1b.txt new file mode 100644 index 00000000..d50b20ce --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-N1b.txt @@ -0,0 +1,287 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (25.13219ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (40.329677ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (17.847648ms) +✔ decide prints a declining choice as declining (16.236026ms) +✔ an unknown outcome is reported once and never resent (14.614685ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.96882ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (20.265592ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.847024ms) +✔ every human command refuses inside an agent run before it touches the bus (19.0666ms) +✔ usage errors exit 4; no business and no host is a usage error (23.771595ms) +✔ agents and tasks print through the broker (22.439025ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (5.615165ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (79.614922ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (50.629582ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (59.956202ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (62.369032ms) +✔ a business without tracker.baseUrl gets no trackers entry (72.803213ms) +✔ an unknown business and a broken system config refuse with exit 3 (91.434873ms) +✔ empty views say so (1.119896ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.39368ms) +✔ tasks print the tracker fields the snapshot carries (0.230495ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (971.408631ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (156.703759ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (82.464757ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (132.155646ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (122.572416ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (93.474394ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (124.413606ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (73.070299ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (136.766699ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.679527ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.920199ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (199.4411ms) +✔ bus start refuses with exit 3 without a notifier config (85.823805ms) +✔ bus start runs until bus stop; status reports it while it runs (598.242788ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.796886ms) +✔ zoned uses the IANA zone across DST (31.356487ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (25.728704ms) +✔ two blocking decisions get two DMs with different nonces (21.23633ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.35172ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (27.227779ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (23.50348ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.462406ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.183037ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (164.8161ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (65.126378ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.207487ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.553097ms) +✔ an inbox read failure is logged and the next poll retries (0.803056ms) +✔ no Discord id reaches the journal or the log (9.552876ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (4.143616ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.828747ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.722799ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.618835ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.666091ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.135547ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.383772ms) +✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.923198ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.497729ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.780238ms) +✔ digest content stays within Discord's 2000 characters (0.332734ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.842936ms) +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 (378.485199ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (60.534995ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2279.507696ms) +✔ busExit and refuseInsideAgent (0.401143ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.711334ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.445569ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.548543ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.553557ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (17.54013ms) +✔ 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.876847ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.350282ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.337891ms) +✔ authorize: open channel, listed user (1.89692ms) +✔ authorize: wrong guild (0.196017ms) +✔ authorize: no guild (DM) (0.159648ms) +✔ authorize: unlisted channel (0.211528ms) +✔ authorize: unknown channel, no info (0.17437ms) +✔ authorize: thread of listed parent (0.186026ms) +✔ authorize: thread of unlisted parent (0.149734ms) +✔ authorize: text channel that is not a thread and not listed (0.146165ms) +✔ authorize: unlisted user (0.190422ms) +✔ authorize: no author (0.287307ms) +✔ authorize: bot author (listed id, bot flag) (0.146782ms) +✔ authorize: system author (0.124802ms) +✔ authorize: the bot itself (0.1379ms) +✔ authorize: webhook (0.10391ms) +✔ authorize: mention channel without mention (0.129963ms) +✔ authorize: mention channel with bot mention (0.132393ms) +✔ authorize: mention channel with @everyone only (0.11567ms) +✔ authorize: mention channel mentioning someone else (0.079587ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.110011ms) +✔ authorize: private thread under mention channel, mentioned (0.105934ms) +✔ authorize: private thread under mention channel, not mentioned (0.08689ms) +✔ authorize: thread in another guild per channel info (0.100341ms) +✔ authorize: not an object (0.069366ms) +✔ authorize: no id (0.059022ms) +✔ authorize: oversize content is accepted and flagged (0.07687ms) +✔ authorize: exactly the limit is not oversize (0.065262ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.475774ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.183429ms) +✔ binding: a complete binding validates and is frozen (2.480725ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.325315ms) +✔ binding: empty allowlists refuse (0.424082ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.312887ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.647852ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.591747ms) +✔ binding: file must be 0600, regular, not a symlink (1.48463ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.603943ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (109.395682ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.189775ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (423.629847ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (253.14589ms) +✔ cli: run refuses when STOP is present, before any network use (159.519301ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.672429ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.221942ms) +✔ 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.249242ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.409761ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.784586ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.624831ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.46645ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.239191ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.115343ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.388532ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.369891ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.31863ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.058028ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (34.555152ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.050443ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.059826ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.062894ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.293209ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.111608ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.96512ms) +✔ 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.176493ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (8.066122ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.899833ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.879016ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.270749ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (4.846363ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.827351ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.893573ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.312639ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.522057ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.193669ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.6605ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (3.084023ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.458312ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (67.390331ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (54.367852ms) +✔ engine: one prompt, one turn, text and usage come back (53.716909ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (372.16094ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (251.624099ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.442123ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.781561ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (130.480676ms) +✔ 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.285834ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.337496ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.266264ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.495014ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.663698ms) +✔ engine: a malformed JSONL line fails the turn, not the process (27.427227ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.004989ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (4.259634ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (3.447212ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.53773ms) +✔ gateway: op 9 resumable resumes (0.345384ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.966945ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.566464ms) +✔ gateway: close() is final and unparseable frames are ignored (0.501479ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (72.158667ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (65.988141ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (170.563911ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.320126ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (92.563422ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (98.500161ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (128.577265ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (269.647215ms) +✔ git: push pushes the named branch only and reports up to date (69.971408ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (91.267221ms) +✔ git: the credential helper answers get over https from a private file and nothing else (201.812402ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (751.369703ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (3.981124ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (88.744096ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.157419ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.632646ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (76.48799ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (394.958079ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.657331ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (32.218768ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.631053ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (54.600895ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (174.674862ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (89.399144ms) +✔ notices: a kind is recorded per UTC day and found again (0.538714ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.288162ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.219281ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.803445ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (52.718397ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (61.85884ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (50.757952ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (81.600707ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (763.284875ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.603613ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.566635ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.703504ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.720263ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.808599ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.694852ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.735946ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.587449ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.949424ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.918762ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.929938ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.588893ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (6.721566ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.697645ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.018695ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1020.639488ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.54979ms) +✔ 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.54055ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (2.957801ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.228015ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.324914ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.294113ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.812717ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (8.630648ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (6.776269ms) +✔ tools: listing and search caps hold (21.511378ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.122692ms) +✔ 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.242925ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.485973ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.648253ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.444422ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.11594ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.084456ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.536126ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.463118ms) +✔ 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.148129ms) +✔ 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.289141ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.236207ms) +✔ 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.675576ms) +✔ 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.296962ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3164.258704 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:384:1 +✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.923198ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: ENOTDIR: not a directory, mkdir '/mnt/storage/scratch/tmp/mosaic-cli-ep4mvm/plain/notify/demo' + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:392: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: Error: ENOTDIR: not a directory, mkdir '/mnt/storage/scratch/tmp/mosaic-cli-ep4mvm/plain/notify/demo' + at mkdirSync (node:fs:1734:26) + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:162:5) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:392: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:392: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), + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-N1c.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-N1c.txt new file mode 100644 index 00000000..f41af6c9 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-N1c.txt @@ -0,0 +1,287 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (28.859169ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (43.522891ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (26.19858ms) +✔ decide prints a declining choice as declining (18.829143ms) +✔ an unknown outcome is reported once and never resent (15.751541ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.438474ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (17.051428ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (19.773923ms) +✔ every human command refuses inside an agent run before it touches the bus (17.28548ms) +✔ usage errors exit 4; no business and no host is a usage error (24.900065ms) +✔ agents and tasks print through the broker (18.520332ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.497874ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (76.618004ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (72.088781ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (64.855255ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (61.500092ms) +✔ a business without tracker.baseUrl gets no trackers entry (61.348789ms) +✔ an unknown business and a broken system config refuse with exit 3 (86.301376ms) +✔ empty views say so (1.090566ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.393387ms) +✔ tasks print the tracker fields the snapshot carries (0.245999ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (985.42783ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (148.671929ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (80.456483ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (129.760165ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (125.886644ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (79.358909ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (123.89411ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (88.464277ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (135.851968ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (28.571507ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.046293ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.624246ms) +✔ bus start refuses with exit 3 without a notifier config (90.134268ms) +✔ bus start runs until bus stop; status reports it while it runs (596.260356ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.19919ms) +✔ zoned uses the IANA zone across DST (26.692816ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (31.916057ms) +✔ two blocking decisions get two DMs with different nonces (32.543329ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.593689ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (20.621592ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (41.181728ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.69903ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (16.84485ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (151.570423ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (61.177872ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (16.092738ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.267999ms) +✔ an inbox read failure is logged and the next poll retries (0.772851ms) +✔ no Discord id reaches the journal or the log (9.717701ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.12873ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.642249ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.748411ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.4724ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.553383ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.824286ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.325175ms) +✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.878089ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.377773ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.454861ms) +✔ digest content stays within Discord's 2000 characters (0.258973ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.361261ms) +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 (392.782821ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (53.652805ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2305.145021ms) +✔ busExit and refuseInsideAgent (0.408181ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (4.267462ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (3.452639ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.845017ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.701646ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.334613ms) +✔ 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.68051ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (5.912514ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.983616ms) +✔ authorize: open channel, listed user (1.817784ms) +✔ authorize: wrong guild (0.211759ms) +✔ authorize: no guild (DM) (0.18151ms) +✔ authorize: unlisted channel (0.186039ms) +✔ authorize: unknown channel, no info (0.168586ms) +✔ authorize: thread of listed parent (0.220159ms) +✔ authorize: thread of unlisted parent (0.158735ms) +✔ authorize: text channel that is not a thread and not listed (0.145594ms) +✔ authorize: unlisted user (0.212654ms) +✔ authorize: no author (0.289283ms) +✔ authorize: bot author (listed id, bot flag) (0.170478ms) +✔ authorize: system author (0.152365ms) +✔ authorize: the bot itself (0.10816ms) +✔ authorize: webhook (0.100938ms) +✔ authorize: mention channel without mention (0.124185ms) +✔ authorize: mention channel with bot mention (0.163965ms) +✔ authorize: mention channel with @everyone only (0.112685ms) +✔ authorize: mention channel mentioning someone else (0.092501ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.097279ms) +✔ authorize: private thread under mention channel, mentioned (0.12121ms) +✔ authorize: private thread under mention channel, not mentioned (0.084618ms) +✔ authorize: thread in another guild per channel info (0.100982ms) +✔ authorize: not an object (0.071546ms) +✔ authorize: no id (0.06563ms) +✔ authorize: oversize content is accepted and flagged (0.072863ms) +✔ authorize: exactly the limit is not oversize (0.071345ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.480647ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.158273ms) +✔ binding: a complete binding validates and is frozen (2.722269ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.311563ms) +✔ binding: empty allowlists refuse (0.389812ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (2.987714ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (3.38944ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.789481ms) +✔ binding: file must be 0600, regular, not a symlink (1.776621ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.683713ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (133.074131ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.216267ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (390.462053ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (211.112392ms) +✔ cli: run refuses when STOP is present, before any network use (136.493498ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.815175ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.223446ms) +✔ 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.897595ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.709019ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.937378ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.261717ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.43824ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.435281ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.188662ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.190739ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (4.526686ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.355518ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (49.257438ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.884449ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.372369ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.206647ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.167803ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.271301ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.39736ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (2.014298ms) +✔ 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.52139ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (4.056645ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.894751ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.786506ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.124475ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.299552ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.898687ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.753624ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.155638ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.329311ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.329615ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.629937ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.60156ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.412031ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (59.470308ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (65.201222ms) +✔ engine: one prompt, one turn, text and usage come back (53.296921ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (362.742469ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.212599ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.379544ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.560913ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.29101ms) +✔ 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.676517ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.267154ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.324488ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.511341ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.115054ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.439637ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.434863ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.598401ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.698896ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.558802ms) +✔ gateway: op 9 resumable resumes (0.41104ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.113347ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.501856ms) +✔ gateway: close() is final and unparseable frames are ignored (0.539476ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (84.848713ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (66.044753ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (134.425936ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.484123ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (97.198044ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.27449ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (98.883283ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (210.094729ms) +✔ git: push pushes the named branch only and reports up to date (69.268475ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (90.899035ms) +✔ git: the credential helper answers get over https from a private file and nothing else (211.208093ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (765.663195ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.21821ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (93.38947ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.324053ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.404865ms) +✔ 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.740808ms) +✔ 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 (348.345567ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.572613ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (29.276208ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.82824ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (48.859496ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (143.702874ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (83.318894ms) +✔ notices: a kind is recorded per UTC day and found again (0.437678ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.53815ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.604759ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.733651ms) +✔ 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.403721ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (54.868213ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (43.315297ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (91.590305ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (670.090674ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.619633ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (7.246855ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.70481ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.714464ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.799529ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.798929ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.065246ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.741474ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.346906ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (30.265903ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.294229ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.769052ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.984127ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.687406ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.248138ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.089592ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.451974ms) +✔ 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.981803ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.253853ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.194411ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.252605ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.872341ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.271195ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.154442ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.774478ms) +✔ tools: listing and search caps hold (11.623179ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.897446ms) +✔ 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.505285ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.539266ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.752099ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.426594ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.109165ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.784497ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.391923ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.726674ms) +✔ 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.721511ms) +✔ 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.249065ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.234037ms) +✔ 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.579097ms) +✔ 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.489611ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3183.915811 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:384:1 +✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.878089ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: EISDIR: illegal operation on a directory, open '/mnt/storage/scratch/tmp/mosaic-cli-OmjGzR/notify/acme/sent.jsonl' + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:395: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: Error: EISDIR: illegal operation on a directory, open '/mnt/storage/scratch/tmp/mosaic-cli-OmjGzR/notify/acme/sent.jsonl' + at openSync (node:fs:779:18) + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:182:10) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:395: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:395: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), + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-N2.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-N2.txt new file mode 100644 index 00000000..816d730c --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-N2.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (34.145901ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (47.775731ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (23.290099ms) +✔ decide prints a declining choice as declining (22.078854ms) +✔ an unknown outcome is reported once and never resent (15.800453ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.512369ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (19.855539ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.272377ms) +✔ every human command refuses inside an agent run before it touches the bus (25.308467ms) +✔ usage errors exit 4; no business and no host is a usage error (21.103783ms) +✔ agents and tasks print through the broker (19.011515ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (5.051234ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (86.869045ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (63.977185ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (53.610588ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (60.499515ms) +✔ a business without tracker.baseUrl gets no trackers entry (64.885946ms) +✔ an unknown business and a broken system config refuse with exit 3 (95.45487ms) +✔ empty views say so (0.998112ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.25455ms) +✔ tasks print the tracker fields the snapshot carries (0.228409ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (984.058106ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (146.819118ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (76.727453ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (124.990947ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (129.654431ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.785161ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (124.690274ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.685271ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (139.433887ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.684881ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.807303ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (200.875958ms) +✔ bus start refuses with exit 3 without a notifier config (88.51793ms) +✔ bus start runs until bus stop; status reports it while it runs (602.262755ms) +✔ bus-service.sh renders the unit and installs it into a given directory (25.852417ms) +✔ zoned uses the IANA zone across DST (27.817773ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (35.228068ms) +✔ two blocking decisions get two DMs with different nonces (21.845993ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.392654ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (21.091273ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (25.404083ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.036092ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.295857ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (149.055471ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (68.583829ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (18.844683ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (11.114662ms) +✔ an inbox read failure is logged and the next poll retries (0.939492ms) +✔ no Discord id reaches the journal or the log (11.316488ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.438592ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.303883ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.783781ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.594691ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.711371ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.226472ms) +✖ the journal: a symlinked directory refuses and says it is a link (0.924893ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.60274ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.415488ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.559483ms) +✔ digest content stays within Discord's 2000 characters (0.314308ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.553163ms) +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 (382.349984ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (65.77689ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2275.536161ms) +✔ busExit and refuseInsideAgent (0.396752ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.865358ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.730839ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.645587ms) +✔ approvals: a button approves only on its own request message with the matching custom id (1.144832ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.286897ms) +✔ 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.785029ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (9.217213ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.035382ms) +✔ authorize: open channel, listed user (1.743476ms) +✔ authorize: wrong guild (0.184716ms) +✔ authorize: no guild (DM) (0.296654ms) +✔ authorize: unlisted channel (0.173389ms) +✔ authorize: unknown channel, no info (0.268458ms) +✔ authorize: thread of listed parent (0.193059ms) +✔ authorize: thread of unlisted parent (0.236881ms) +✔ authorize: text channel that is not a thread and not listed (0.125466ms) +✔ authorize: unlisted user (0.348601ms) +✔ authorize: no author (0.353029ms) +✔ authorize: bot author (listed id, bot flag) (0.482526ms) +✔ authorize: system author (0.12468ms) +✔ authorize: the bot itself (0.089956ms) +✔ authorize: webhook (0.089729ms) +✔ authorize: mention channel without mention (0.26233ms) +✔ authorize: mention channel with bot mention (0.173457ms) +✔ authorize: mention channel with @everyone only (0.090435ms) +✔ authorize: mention channel mentioning someone else (0.099016ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.072492ms) +✔ authorize: private thread under mention channel, mentioned (0.081666ms) +✔ authorize: private thread under mention channel, not mentioned (0.061721ms) +✔ authorize: thread in another guild per channel info (0.064458ms) +✔ authorize: not an object (0.058966ms) +✔ authorize: no id (0.060906ms) +✔ authorize: oversize content is accepted and flagged (0.06914ms) +✔ authorize: exactly the limit is not oversize (0.065672ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.444027ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.151302ms) +✔ binding: a complete binding validates and is frozen (2.523579ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.263667ms) +✔ binding: empty allowlists refuse (0.401048ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.289347ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.532389ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.478841ms) +✔ binding: file must be 0600, regular, not a symlink (1.82097ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.977497ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (118.296279ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.397965ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (411.521282ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (194.550815ms) +✔ cli: run refuses when STOP is present, before any network use (137.779632ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.974567ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.331492ms) +✔ 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.704601ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (19.381665ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.854148ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.448128ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.676395ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.10638ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.353066ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.258277ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.485906ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.295699ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.441789ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.751289ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.085765ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.86994ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.702837ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.307418ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.953891ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.08887ms) +✔ 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.630868ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.42889ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.417568ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (5.335304ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.443952ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (3.198098ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.817768ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.091539ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.905905ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.501795ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.130302ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.657428ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.38731ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.426603ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (78.967225ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (52.756224ms) +✔ engine: one prompt, one turn, text and usage come back (53.36595ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (369.503573ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (232.013615ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.532393ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.448361ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.909456ms) +✔ 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.311518ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.608651ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.357412ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.50683ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.105228ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.468989ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.380584ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.994664ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (3.166413ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (1.288407ms) +✔ gateway: op 9 resumable resumes (0.433182ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.871737ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.533253ms) +✔ gateway: close() is final and unparseable frames are ignored (0.389817ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (74.178775ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (61.149552ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (139.63557ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.475845ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (104.399929ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (90.164396ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (85.522973ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (196.97209ms) +✔ git: push pushes the named branch only and reports up to date (70.762086ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (86.674254ms) +✔ git: the credential helper answers get over https from a private file and nothing else (206.393074ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (731.229421ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (6.477414ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (89.953235ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.449324ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.198128ms) +✔ 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.418246ms) +✔ 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 (337.937949ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.458647ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (22.278618ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.969196ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (37.649373ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (144.799697ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (87.006079ms) +✔ notices: a kind is recorded per UTC day and found again (0.484099ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.313965ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.115607ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.821127ms) +✔ 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.596185ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (59.831781ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (47.880019ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (84.500653ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (647.531044ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.486496ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.502554ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.670847ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.795543ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.918446ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.375599ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.438184ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.351938ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.312775ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.677544ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.871377ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.053667ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.467615ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.691971ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.509863ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.970826ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.440192ms) +✔ 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.486217ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.23785ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.20178ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.824088ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.713981ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.211396ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.380258ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.914022ms) +✔ tools: listing and search caps hold (11.492148ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.929013ms) +✔ 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.212559ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.362676ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.405731ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.365431ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.934561ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.869419ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.863278ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (3.948662ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1032.943649ms) +✔ 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.171229ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.232197ms) +✔ 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.681056ms) +✔ 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.873492ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3143.804027 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:373:1 +✖ the journal: a symlinked directory refuses and says it is a link (0.924893ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:380: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/r2/mut-N4.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-N4.txt new file mode 100644 index 00000000..0edf48b4 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-N4.txt @@ -0,0 +1,273 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (28.737033ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (35.163479ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.102508ms) +✔ decide prints a declining choice as declining (25.358311ms) +✔ an unknown outcome is reported once and never resent (19.13216ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.11116ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.68501ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (16.604187ms) +✔ every human command refuses inside an agent run before it touches the bus (17.389564ms) +✔ usage errors exit 4; no business and no host is a usage error (18.438398ms) +✔ agents and tasks print through the broker (17.364042ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.823383ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (79.301366ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.327636ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (56.690964ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.286999ms) +✔ a business without tracker.baseUrl gets no trackers entry (74.637089ms) +✔ an unknown business and a broken system config refuse with exit 3 (85.803721ms) +✔ empty views say so (1.012118ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.271847ms) +✔ tasks print the tracker fields the snapshot carries (0.278744ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (966.100787ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (140.450345ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (78.156839ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (133.865834ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.350771ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (77.326819ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (122.516647ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (86.335457ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (136.683399ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (25.080292ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.356572ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (223.727265ms) +✔ bus start refuses with exit 3 without a notifier config (96.900998ms) +✔ bus start runs until bus stop; status reports it while it runs (604.558178ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.650399ms) +✔ zoned uses the IANA zone across DST (30.182663ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (24.704189ms) +✔ two blocking decisions get two DMs with different nonces (22.481546ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.320562ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (23.793991ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (31.135777ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (30.905443ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.455431ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (155.226466ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (58.182106ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.722414ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.65291ms) +✔ an inbox read failure is logged and the next poll retries (0.713996ms) +✔ no Discord id reaches the journal or the log (9.632608ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (1.994709ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.566077ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.628868ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.494324ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.632544ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.218066ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.367964ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.513813ms) +✖ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.868388ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.499484ms) +✔ digest content stays within Discord's 2000 characters (0.271897ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.946353ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (363.490563ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (54.836406ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2251.792796ms) +✔ busExit and refuseInsideAgent (0.415315ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (2.997865ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.333382ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.473973ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.431141ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.162547ms) +✔ 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.879811ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (10.405ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.102995ms) +✔ authorize: open channel, listed user (1.735923ms) +✔ authorize: wrong guild (0.180339ms) +✔ authorize: no guild (DM) (0.286307ms) +✔ authorize: unlisted channel (0.20056ms) +✔ authorize: unknown channel, no info (0.170004ms) +✔ authorize: thread of listed parent (0.18518ms) +✔ authorize: thread of unlisted parent (0.146331ms) +✔ authorize: text channel that is not a thread and not listed (0.151178ms) +✔ authorize: unlisted user (0.282805ms) +✔ authorize: no author (0.27851ms) +✔ authorize: bot author (listed id, bot flag) (0.178332ms) +✔ authorize: system author (0.462125ms) +✔ authorize: the bot itself (0.09571ms) +✔ authorize: webhook (0.094039ms) +✔ authorize: mention channel without mention (0.205392ms) +✔ authorize: mention channel with bot mention (0.13218ms) +✔ authorize: mention channel with @everyone only (0.220732ms) +✔ authorize: mention channel mentioning someone else (0.072694ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.109539ms) +✔ authorize: private thread under mention channel, mentioned (0.0936ms) +✔ authorize: private thread under mention channel, not mentioned (0.098945ms) +✔ authorize: thread in another guild per channel info (0.094707ms) +✔ authorize: not an object (0.078398ms) +✔ authorize: no id (0.082876ms) +✔ authorize: oversize content is accepted and flagged (0.07022ms) +✔ authorize: exactly the limit is not oversize (0.068912ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.460012ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.136121ms) +✔ binding: a complete binding validates and is frozen (2.674386ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.306358ms) +✔ binding: empty allowlists refuse (0.360495ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.236208ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.721003ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.462088ms) +✔ binding: file must be 0600, regular, not a symlink (1.920643ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.372535ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (105.709582ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.49337ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (400.329376ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (191.864176ms) +✔ cli: run refuses when STOP is present, before any network use (124.815718ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.901816ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.160395ms) +✔ 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.079564ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.176383ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.959322ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.154588ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.416762ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.548332ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (35.099227ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.910759ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.978617ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.817652ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.228979ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (35.631341ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.862583ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (6.208461ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.056737ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (9.961835ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (5.850278ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.221581ms) +✔ 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.848345ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.72145ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.967844ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.705275ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (6.680507ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.463619ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.758355ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.726393ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.126448ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.36912ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.085982ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.635253ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.586546ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.480372ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (56.372209ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (55.646961ms) +✔ engine: one prompt, one turn, text and usage come back (60.189032ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (360.407805ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (229.179405ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.027644ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (229.755165ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.50392ms) +✔ 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.795487ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.116051ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.339152ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.504355ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (430.36146ms) +✔ engine: a malformed JSONL line fails the turn, not the process (31.30779ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (49.029622ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (1.998981ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.888521ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.513941ms) +✔ gateway: op 9 resumable resumes (0.353007ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.882529ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.49968ms) +✔ gateway: close() is final and unparseable frames are ignored (0.350207ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (76.166016ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (71.065158ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (159.994097ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (1.188851ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (93.420476ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (85.835549ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (82.56582ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (196.144674ms) +✔ git: push pushes the named branch only and reports up to date (64.371408ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (85.736535ms) +✔ git: the credential helper answers get over https from a private file and nothing else (203.13621ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (759.727611ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.345198ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (79.066423ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.927796ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.741239ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (54.643126ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (342.081483ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.393045ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.478182ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.064208ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (40.02632ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (123.791284ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (83.04366ms) +✔ notices: a kind is recorded per UTC day and found again (0.48889ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.290246ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.762358ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.595804ms) +✔ 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.614945ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (59.971798ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (50.345009ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (91.724962ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (624.974757ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.103167ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.356734ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.632049ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.725331ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.816187ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.5774ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.826071ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (2.267287ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.038848ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (32.762066ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (14.048738ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (11.981418ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.886959ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.669385ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.270776ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1016.355023ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.420149ms) +✔ 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.038896ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.430564ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.207487ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.632782ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.666314ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.070401ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.73592ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.959728ms) +✔ tools: listing and search caps hold (12.331689ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.146949ms) +✔ 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.950845ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.209795ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.404605ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (8.08911ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.140938ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (2.597675ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.836394ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.278616ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1035.670196ms) +✔ 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.288103ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.55435ms) +✔ 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.729683ms) +✔ 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.248629ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3181.332561 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:398:1 +✖ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.868388ms) + AssertionError [ERR_ASSERTION]: Missing expected exception. + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:406: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/r2/mut-N5.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-N5.txt new file mode 100644 index 00000000..1f5be931 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-N5.txt @@ -0,0 +1,282 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (29.794649ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (38.94218ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (36.490099ms) +✔ decide prints a declining choice as declining (38.340314ms) +✔ an unknown outcome is reported once and never resent (24.278352ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (16.469313ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (21.948304ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (21.275067ms) +✔ every human command refuses inside an agent run before it touches the bus (16.125688ms) +✔ usage errors exit 4; no business and no host is a usage error (23.9681ms) +✔ agents and tasks print through the broker (15.874847ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.526583ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (95.999475ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (76.139065ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (68.224936ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (56.183516ms) +✔ a business without tracker.baseUrl gets no trackers entry (59.35911ms) +✔ an unknown business and a broken system config refuse with exit 3 (95.446003ms) +✔ empty views say so (1.218516ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (2.365312ms) +✔ tasks print the tracker fields the snapshot carries (0.263557ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1021.348478ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (156.932518ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (83.638475ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (139.574988ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (139.856077ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (72.008017ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (131.170086ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (71.932431ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (143.292727ms) +✖ watchChildren reports a child that died before it was called, and one that dies later (26.659105ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.753341ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (217.069241ms) +✔ bus start refuses with exit 3 without a notifier config (117.770349ms) +✔ bus start runs until bus stop; status reports it while it runs (627.610933ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.963314ms) +✔ zoned uses the IANA zone across DST (27.268366ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (40.291171ms) +✔ two blocking decisions get two DMs with different nonces (23.386255ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.313306ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (27.950556ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (60.035804ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (27.619225ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.393879ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (155.418551ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (69.101062ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (21.171124ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.111513ms) +✔ an inbox read failure is logged and the next poll retries (1.065946ms) +✔ no Discord id reaches the journal or the log (11.034042ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.385735ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.873808ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.398396ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.770734ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.791724ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.349674ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.658124ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.799959ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.60515ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.752058ms) +✔ digest content stays within Discord's 2000 characters (0.474371ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.732754ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (406.738827ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (68.919135ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2272.81185ms) +✔ busExit and refuseInsideAgent (0.396368ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.115426ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.864066ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.639613ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.476948ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (23.164584ms) +✔ 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.803285ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.567729ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (3.874416ms) +✔ authorize: open channel, listed user (2.374951ms) +✔ authorize: wrong guild (0.203186ms) +✔ authorize: no guild (DM) (0.314521ms) +✔ authorize: unlisted channel (0.184651ms) +✔ authorize: unknown channel, no info (0.264493ms) +✔ authorize: thread of listed parent (0.226501ms) +✔ authorize: thread of unlisted parent (0.172718ms) +✔ authorize: text channel that is not a thread and not listed (0.189471ms) +✔ authorize: unlisted user (1.168464ms) +✔ authorize: no author (0.360857ms) +✔ authorize: bot author (listed id, bot flag) (0.149738ms) +✔ authorize: system author (0.119963ms) +✔ authorize: the bot itself (0.086895ms) +✔ authorize: webhook (0.274611ms) +✔ authorize: mention channel without mention (0.730675ms) +✔ authorize: mention channel with bot mention (0.130524ms) +✔ authorize: mention channel with @everyone only (0.083595ms) +✔ authorize: mention channel mentioning someone else (0.09759ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.075999ms) +✔ authorize: private thread under mention channel, mentioned (0.082108ms) +✔ authorize: private thread under mention channel, not mentioned (0.091275ms) +✔ authorize: thread in another guild per channel info (0.077608ms) +✔ authorize: not an object (0.063892ms) +✔ authorize: no id (0.064984ms) +✔ authorize: oversize content is accepted and flagged (0.06103ms) +✔ authorize: exactly the limit is not oversize (0.062682ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.471043ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.151618ms) +✔ binding: a complete binding validates and is frozen (2.41465ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.237917ms) +✔ binding: empty allowlists refuse (0.631065ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.313639ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.781488ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.786813ms) +✔ binding: file must be 0600, regular, not a symlink (1.559581ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.901167ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (112.038118ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (4.594124ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (459.944523ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (200.449425ms) +✔ cli: run refuses when STOP is present, before any network use (131.496934ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.731461ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.230436ms) +✔ delivery: an accepted message is in the inbox before the turn, the reply is chunked with one nonce per chunk, and the turn record is write-once (16.861301ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (31.031611ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.579151ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.643543ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.532429ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (4.598246ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.438061ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (4.353558ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.936551ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.123755ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.673073ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.606126ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (6.177237ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (5.319998ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.617264ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.38716ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.348666ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.959916ms) +✔ 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.743145ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.751288ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.457921ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (2.906819ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.713777ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.5633ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.867817ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.8518ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.164419ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.43115ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.480447ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.901129ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.348155ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.43701ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (67.985039ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (57.926809ms) +✔ engine: one prompt, one turn, text and usage come back (72.056593ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (365.551435ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (228.213673ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.766667ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (230.964112ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (128.137334ms) +✔ 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.846242ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.452263ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.329494ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.514545ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.338658ms) +✔ engine: a malformed JSONL line fails the turn, not the process (23.521958ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (52.248775ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.558948ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.757563ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.491835ms) +✔ gateway: op 9 resumable resumes (0.384894ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.010785ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.50756ms) +✔ gateway: close() is final and unparseable frames are ignored (0.485292ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (103.893504ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (68.936312ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (118.493364ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.709579ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (107.394682ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (86.981856ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (91.299865ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (196.742625ms) +✔ git: push pushes the named branch only and reports up to date (69.575682ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (82.575356ms) +✔ git: the credential helper answers get over https from a private file and nothing else (221.530799ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (801.329172ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (5.008682ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (105.008354ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.157561ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.293655ms) +✔ 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.603737ms) +✔ 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 (371.422885ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.379133ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.900067ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.239444ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (39.851252ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (137.413265ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (85.013967ms) +✔ notices: a kind is recorded per UTC day and found again (0.417344ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.485599ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (3.033017ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.850845ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.666066ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (54.668767ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (49.548825ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (109.641255ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (676.379003ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.635557ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.688728ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.873323ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.679665ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.844333ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.973939ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.542815ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.83329ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.200911ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (27.392124ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.169765ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (7.586821ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.356442ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.688455ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.149597ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1014.059454ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.438188ms) +✔ 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.290361ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.198511ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.205713ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.264933ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.490622ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (5.283832ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.407578ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.549193ms) +✔ tools: listing and search caps hold (13.976203ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.912012ms) +✔ 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.373079ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.473472ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.267278ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.91166ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (4.837165ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.993099ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.479683ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.898109ms) +✔ 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.53154ms) +✔ 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.812264ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.355601ms) +✔ 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.014188ms) +✔ 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.093352ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3330.138768 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:314:1 +✖ watchChildren reports a child that died before it was called, and one that dies later (26.659105ms) + 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:323: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/r2/mut-R2a.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-R2a.txt new file mode 100644 index 00000000..59d6aaf2 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-R2a.txt @@ -0,0 +1,290 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (30.266926ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (45.66417ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.856626ms) +✔ decide prints a declining choice as declining (20.024767ms) +✔ an unknown outcome is reported once and never resent (14.773851ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (17.412784ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (16.826666ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (14.717837ms) +✔ every human command refuses inside an agent run before it touches the bus (13.214516ms) +✔ usage errors exit 4; no business and no host is a usage error (18.808117ms) +✔ agents and tasks print through the broker (21.378996ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (5.916491ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (68.898749ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (71.363969ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (57.75907ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (51.943641ms) +✔ a business without tracker.baseUrl gets no trackers entry (57.687842ms) +✔ an unknown business and a broken system config refuse with exit 3 (89.099702ms) +✔ empty views say so (1.125502ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.538518ms) +✔ tasks print the tracker fields the snapshot carries (0.2228ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (979.821029ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (152.727215ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.070305ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (127.687377ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.155382ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.116208ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (134.247025ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (71.896615ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (137.451352ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (22.554712ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.798101ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (198.402743ms) +✔ bus start refuses with exit 3 without a notifier config (85.719086ms) +✔ bus start runs until bus stop; status reports it while it runs (595.430698ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.652828ms) +✔ zoned uses the IANA zone across DST (24.726385ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (29.152423ms) +✔ two blocking decisions get two DMs with different nonces (25.104303ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.327336ms) +✖ a failed DM is journaled, backs off, and is retried until it lands (35.595976ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (29.550213ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (28.075564ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (18.657591ms) +✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (145.611266ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (60.533301ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (14.616093ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.927771ms) +✔ an inbox read failure is logged and the next poll retries (1.064229ms) +✔ no Discord id reaches the journal or the log (9.423783ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.450495ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.148722ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.323057ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.50755ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.563077ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.890067ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.412297ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.471954ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.367338ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.43487ms) +✔ digest content stays within Discord's 2000 characters (0.282615ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.129836ms) +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 (379.12536ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (64.309331ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2261.223759ms) +✔ busExit and refuseInsideAgent (0.401724ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.625669ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.826441ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.662925ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.505367ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.068818ms) +✔ 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.204007ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.869574ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.944775ms) +✔ authorize: open channel, listed user (1.754008ms) +✔ authorize: wrong guild (0.332026ms) +✔ authorize: no guild (DM) (0.193086ms) +✔ authorize: unlisted channel (0.178597ms) +✔ authorize: unknown channel, no info (0.173256ms) +✔ authorize: thread of listed parent (0.199811ms) +✔ authorize: thread of unlisted parent (0.189604ms) +✔ authorize: text channel that is not a thread and not listed (0.148632ms) +✔ authorize: unlisted user (0.19263ms) +✔ authorize: no author (0.331679ms) +✔ authorize: bot author (listed id, bot flag) (0.203138ms) +✔ authorize: system author (0.112449ms) +✔ authorize: the bot itself (0.107046ms) +✔ authorize: webhook (0.078295ms) +✔ authorize: mention channel without mention (0.114163ms) +✔ authorize: mention channel with bot mention (0.134884ms) +✔ authorize: mention channel with @everyone only (0.124096ms) +✔ authorize: mention channel mentioning someone else (0.11984ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.094402ms) +✔ authorize: private thread under mention channel, mentioned (0.072586ms) +✔ authorize: private thread under mention channel, not mentioned (0.2045ms) +✔ authorize: thread in another guild per channel info (0.061572ms) +✔ authorize: not an object (0.056638ms) +✔ authorize: no id (0.050415ms) +✔ authorize: oversize content is accepted and flagged (0.059112ms) +✔ authorize: exactly the limit is not oversize (0.053124ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.522411ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.143517ms) +✔ binding: a complete binding validates and is frozen (2.50615ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.406254ms) +✔ binding: empty allowlists refuse (0.393586ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.314708ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.867263ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.536018ms) +✔ binding: file must be 0600, regular, not a symlink (1.867824ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.349392ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (113.67623ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.98223ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (395.157322ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (201.578655ms) +✔ cli: run refuses when STOP is present, before any network use (140.794764ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.735771ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.09323ms) +✔ 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.937731ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.751751ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.441554ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (2.113564ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.664805ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.320447ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.950871ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.1345ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.028243ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.016164ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (45.347222ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (35.309874ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.466538ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.882997ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.167248ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.589504ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.571267ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.934983ms) +✔ 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.191145ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.527371ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (6.682676ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (4.58346ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (8.265489ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.591628ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.800393ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.901467ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.220002ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.452872ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.287682ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (3.287543ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.641894ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.471487ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (60.916789ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (59.448834ms) +✔ engine: one prompt, one turn, text and usage come back (48.312646ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (354.971161ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (233.138971ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (104.037246ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.741085ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.568859ms) +✔ 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.499258ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.510265ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.569479ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.532706ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (424.943568ms) +✔ engine: a malformed JSONL line fails the turn, not the process (26.739074ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (46.351166ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (3.094678ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.13718ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.849978ms) +✔ gateway: op 9 resumable resumes (0.439768ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.915492ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.673117ms) +✔ gateway: close() is final and unparseable frames are ignored (0.393606ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (91.065486ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (72.446294ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (124.799928ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.432106ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (90.989316ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (91.183644ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (100.13924ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (212.267433ms) +✔ git: push pushes the named branch only and reports up to date (64.430551ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (79.208924ms) +✔ git: the credential helper answers get over https from a private file and nothing else (204.378212ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (750.14077ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.477235ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (97.71233ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.381396ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.158657ms) +✔ 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.604439ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (342.045322ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.423641ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (23.41472ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.399657ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (50.239489ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (139.135309ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (86.025792ms) +✔ notices: a kind is recorded per UTC day and found again (0.415185ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (4.593522ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (1.867258ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.246599ms) +✔ 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.201975ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (55.320297ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (39.592411ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (79.198989ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (669.553856ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.326724ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.511643ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.596354ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.712157ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.149145ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.92729ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (2.952258ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.914605ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.341751ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.552894ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.513095ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.346809ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.58156ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.904232ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.244152ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1015.260478ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.405047ms) +✔ 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.013748ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.301499ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.213923ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.229473ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.08884ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.051327ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.231214ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.598406ms) +✔ tools: listing and search caps hold (11.303745ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.023481ms) +✔ 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.959723ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.377975ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.383195ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.391934ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.697706ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.450399ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.705902ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.274213ms) +✔ 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.356016ms) +✔ 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.295926ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.211448ms) +✔ 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.720934ms) +✔ 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.211507ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 242 +ℹ fail 2 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3154.843627 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:90:1 +✖ a failed DM is journaled, backs off, and is retried until it lands (35.595976ms) + AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value: + + assert.ok(s.logs.some((l) => /refused \(HTTP 403\); retry in 1800 s/.test(l))) + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:106: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: false, + expected: true, + operator: '==', + diff: 'simple' + } + +test at packages/cli/tests/notifier.test.mjs:189:1 +✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (145.611266ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + 0 !== 4 + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:198: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: 0, + expected: 4, + operator: 'strictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-R2b.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-R2b.txt new file mode 100644 index 00000000..4b1e4e50 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-R2b.txt @@ -0,0 +1,277 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (28.161834ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (34.245716ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (20.035247ms) +✔ decide prints a declining choice as declining (19.891429ms) +✔ an unknown outcome is reported once and never resent (17.257652ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (20.475501ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (18.291156ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (18.810661ms) +✔ every human command refuses inside an agent run before it touches the bus (26.300885ms) +✔ usage errors exit 4; no business and no host is a usage error (21.769095ms) +✔ agents and tasks print through the broker (16.052438ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (4.070747ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (71.132011ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (56.117223ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (67.654898ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (58.652035ms) +✔ a business without tracker.baseUrl gets no trackers entry (61.9398ms) +✔ an unknown business and a broken system config refuse with exit 3 (90.565117ms) +✔ empty views say so (1.050744ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.353949ms) +✔ tasks print the tracker fields the snapshot carries (0.227491ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (985.301532ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (144.077175ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (76.333324ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (131.950258ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (132.857416ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (82.667306ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (122.687567ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (83.674749ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (142.595623ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (26.5632ms) +✔ bus stop refuses to signal a live pid that is not a bus host (203.026821ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (206.257684ms) +✔ bus start refuses with exit 3 without a notifier config (90.012691ms) +✔ bus start runs until bus stop; status reports it while it runs (596.03406ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.161421ms) +✔ zoned uses the IANA zone across DST (25.429466ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (31.691106ms) +✔ two blocking decisions get two DMs with different nonces (20.083014ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.319233ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (17.218038ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (24.851202ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (30.574446ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.555597ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (158.019254ms) +✖ a restart after the second refusal does not send before that refusal's 30 min are up (67.241172ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (20.292854ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.902027ms) +✔ an inbox read failure is logged and the next poll retries (0.789252ms) +✔ no Discord id reaches the journal or the log (14.374523ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (3.478343ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (3.574695ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.696529ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.758949ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (1.311343ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.666022ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.453856ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.609212ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.436278ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.700138ms) +✔ digest content stays within Discord's 2000 characters (0.330256ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.126458ms) +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 (382.420474ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (55.083236ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2276.076705ms) +✔ busExit and refuseInsideAgent (0.39123ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.58026ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.486348ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.782136ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.534211ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.814758ms) +✔ 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.814314ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (8.21186ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.842547ms) +✔ authorize: open channel, listed user (2.053887ms) +✔ authorize: wrong guild (0.202343ms) +✔ authorize: no guild (DM) (0.170271ms) +✔ authorize: unlisted channel (0.176836ms) +✔ authorize: unknown channel, no info (0.168717ms) +✔ authorize: thread of listed parent (0.196506ms) +✔ authorize: thread of unlisted parent (0.15738ms) +✔ authorize: text channel that is not a thread and not listed (0.171675ms) +✔ authorize: unlisted user (0.178809ms) +✔ authorize: no author (0.557715ms) +✔ authorize: bot author (listed id, bot flag) (0.241612ms) +✔ authorize: system author (0.147439ms) +✔ authorize: the bot itself (0.086306ms) +✔ authorize: webhook (0.118423ms) +✔ authorize: mention channel without mention (0.132895ms) +✔ authorize: mention channel with bot mention (0.143843ms) +✔ authorize: mention channel with @everyone only (0.10657ms) +✔ authorize: mention channel mentioning someone else (0.077662ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.12351ms) +✔ authorize: private thread under mention channel, mentioned (0.105465ms) +✔ authorize: private thread under mention channel, not mentioned (0.086038ms) +✔ authorize: thread in another guild per channel info (0.079013ms) +✔ authorize: not an object (0.065778ms) +✔ authorize: no id (0.066375ms) +✔ authorize: oversize content is accepted and flagged (0.074059ms) +✔ authorize: exactly the limit is not oversize (0.066989ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.565552ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.143183ms) +✔ binding: a complete binding validates and is frozen (2.656805ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.389076ms) +✔ binding: empty allowlists refuse (0.460087ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.229812ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (2.075655ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.256022ms) +✔ binding: file must be 0600, regular, not a symlink (1.821351ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.562417ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (112.924765ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.210818ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (426.874918ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (191.593308ms) +✔ cli: run refuses when STOP is present, before any network use (130.090866ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.534971ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.354584ms) +✔ 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.977314ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (15.608763ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.407972ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.0526ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.277071ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.142759ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.723998ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.595978ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (2.116744ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.196179ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (43.671622ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (37.121541ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.730249ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (8.609241ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (4.780897ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (8.261007ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.238407ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.880169ms) +✔ 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.537315ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.090148ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.805543ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (3.586145ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.446411ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.670732ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.808101ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.880131ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.187134ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.839132ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (2.040833ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.81323ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.216171ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.448431ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (52.681258ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (64.340402ms) +✔ engine: one prompt, one turn, text and usage come back (57.681623ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (356.924501ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (228.644049ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.734148ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (227.115891ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (129.923051ms) +✔ 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.844154ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.259305ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.325464ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.518519ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (425.063998ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.407984ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.760636ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.422469ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.559985ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.47645ms) +✔ gateway: op 9 resumable resumes (0.356614ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.871165ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.526328ms) +✔ gateway: close() is final and unparseable frames are ignored (0.386158ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (92.075027ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (78.749711ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (125.588179ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.477862ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (116.633883ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (87.294943ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (83.764409ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (203.159841ms) +✔ git: push pushes the named branch only and reports up to date (64.374856ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (78.940244ms) +✔ git: the credential helper answers get over https from a private file and nothing else (202.792825ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (760.407252ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.505567ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (101.330217ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.204087ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (4.150497ms) +✔ 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.528632ms) +✔ 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 (346.854604ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.450419ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (26.765004ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.993567ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (38.037028ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (136.745993ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (91.597577ms) +✔ notices: a kind is recorded per UTC day and found again (0.407662ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (6.206925ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.98158ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (2.101575ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (52.865116ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (56.187766ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (42.06983ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (84.559484ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (666.190228ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.458366ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.953106ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.843553ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.788709ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.967209ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (6.08603ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.294386ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.808292ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.003245ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (29.758678ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (12.575322ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (11.292108ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.305426ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.917032ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (6.21851ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.910582ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.450772ms) +✔ 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.992484ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.226257ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.24979ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.326927ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.172966ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.869575ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (8.252117ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.613627ms) +✔ tools: listing and search caps hold (13.011719ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (1.010923ms) +✔ 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.862506ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.464254ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.237546ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (7.302745ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.697192ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.944694ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.569048ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.387757ms) +✔ 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.46693ms) +✔ 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.438251ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.259496ms) +✔ 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.814276ms) +✔ 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.295039ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3187.624351 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:201:1 +✖ a restart after the second refusal does not send before that refusal's 30 min are up (67.241172ms) + AssertionError [ERR_ASSERTION]: nothing between the restart and +60 min + + actual - expected + + [ + + 31.5 + - 60 + ] + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:208: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: [ 31.5 ], + expected: [ 60 ], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-R2c.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-R2c.txt new file mode 100644 index 00000000..1549baa5 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-R2c.txt @@ -0,0 +1,277 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (40.685297ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (37.484026ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (16.605654ms) +✔ decide prints a declining choice as declining (19.393014ms) +✔ an unknown outcome is reported once and never resent (17.852982ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (15.710979ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (29.266905ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (21.009526ms) +✔ every human command refuses inside an agent run before it touches the bus (24.758986ms) +✔ usage errors exit 4; no business and no host is a usage error (24.953573ms) +✔ agents and tasks print through the broker (22.864154ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.258424ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (76.228084ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (58.166251ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (58.550672ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (64.331044ms) +✔ a business without tracker.baseUrl gets no trackers entry (75.92909ms) +✔ an unknown business and a broken system config refuse with exit 3 (82.458244ms) +✔ empty views say so (1.087409ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.359589ms) +✔ tasks print the tracker fields the snapshot carries (0.225154ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (993.897545ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (152.938597ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (88.126377ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (137.108011ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (123.170596ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (73.85963ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (125.758845ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.267818ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (134.516445ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.177914ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.983501ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (205.420751ms) +✔ bus start refuses with exit 3 without a notifier config (88.934962ms) +✔ bus start runs until bus stop; status reports it while it runs (606.927416ms) +✔ bus-service.sh renders the unit and installs it into a given directory (26.293005ms) +✔ zoned uses the IANA zone across DST (25.704583ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (31.460666ms) +✔ two blocking decisions get two DMs with different nonces (30.675203ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.650589ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (25.821533ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (22.378617ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (25.083244ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (16.516437ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (157.893559ms) +✖ a restart after the second refusal does not send before that refusal's 30 min are up (63.495042ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (19.204607ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (10.886139ms) +✔ an inbox read failure is logged and the next poll retries (0.814837ms) +✔ no Discord id reaches the journal or the log (10.08282ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.04334ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.911381ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.771593ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.544469ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.622522ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (2.143025ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.363422ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.556486ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.400861ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.481441ms) +✔ digest content stays within Discord's 2000 characters (0.293111ms) +✔ runLoop never overlaps ticks and stops after the one in flight (110.51501ms) +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 (379.245614ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (54.669042ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2285.747948ms) +✔ busExit and refuseInsideAgent (0.410368ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.374242ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.83547ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.705412ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.511744ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (18.198846ms) +✔ 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.5266ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (9.967294ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.589031ms) +✔ authorize: open channel, listed user (1.93302ms) +✔ authorize: wrong guild (0.178537ms) +✔ authorize: no guild (DM) (0.292093ms) +✔ authorize: unlisted channel (0.179326ms) +✔ authorize: unknown channel, no info (0.28383ms) +✔ authorize: thread of listed parent (0.195098ms) +✔ authorize: thread of unlisted parent (0.241817ms) +✔ authorize: text channel that is not a thread and not listed (0.13626ms) +✔ authorize: unlisted user (0.84987ms) +✔ authorize: no author (0.770423ms) +✔ authorize: bot author (listed id, bot flag) (0.147142ms) +✔ authorize: system author (0.13532ms) +✔ authorize: the bot itself (0.103191ms) +✔ authorize: webhook (0.113308ms) +✔ authorize: mention channel without mention (0.146621ms) +✔ authorize: mention channel with bot mention (1.211073ms) +✔ authorize: mention channel with @everyone only (0.099513ms) +✔ authorize: mention channel mentioning someone else (0.071029ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.06969ms) +✔ authorize: private thread under mention channel, mentioned (0.079615ms) +✔ authorize: private thread under mention channel, not mentioned (0.057973ms) +✔ authorize: thread in another guild per channel info (0.065292ms) +✔ authorize: not an object (0.058305ms) +✔ authorize: no id (0.059287ms) +✔ authorize: oversize content is accepted and flagged (0.067696ms) +✔ authorize: exactly the limit is not oversize (0.066163ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.462736ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.148095ms) +✔ binding: a complete binding validates and is frozen (2.679076ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.750459ms) +✔ binding: empty allowlists refuse (0.397568ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.42358ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.835916ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.838789ms) +✔ binding: file must be 0600, regular, not a symlink (1.768289ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.242621ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (100.203101ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.042691ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (416.198477ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (188.38447ms) +✔ cli: run refuses when STOP is present, before any network use (136.668768ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.702683ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.180138ms) +✔ 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.584103ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (18.902147ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.485136ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.29353ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.377209ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.244496ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.28341ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.977577ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.155398ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.51738ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.464805ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.459075ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (8.694062ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (6.111067ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.492061ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (7.839407ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.100361ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.836243ms) +✔ 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.161106ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.17244ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.895196ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (4.386984ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.567214ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.789429ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.943608ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.051884ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.284424ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.523534ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.574205ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.897148ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.447591ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.425901ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (53.517271ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (54.764108ms) +✔ engine: one prompt, one turn, text and usage come back (60.690738ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (369.555677ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (228.567122ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (103.590577ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.506668ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (130.452014ms) +✔ 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.77209ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (613.605397ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.379673ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.557131ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.116525ms) +✔ engine: a malformed JSONL line fails the turn, not the process (26.089845ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (44.887003ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.560753ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.638311ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.500406ms) +✔ gateway: op 9 resumable resumes (0.384574ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.85435ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.552787ms) +✔ gateway: close() is final and unparseable frames are ignored (0.540151ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (71.620654ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (83.665217ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (143.858301ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.442071ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (93.475596ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (85.861118ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (86.371812ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (195.730915ms) +✔ git: push pushes the named branch only and reports up to date (72.457254ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (87.279071ms) +✔ git: the credential helper answers get over https from a private file and nothing else (213.658166ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (768.286621ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.724785ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (92.605153ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.117381ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.133155ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (74.213903ms) +✔ 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.688011ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.400101ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.700856ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.682573ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (40.085875ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (146.934264ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (88.516909ms) +✔ notices: a kind is recorded per UTC day and found again (0.444021ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.260902ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.702818ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.979157ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (51.549499ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (75.472143ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (50.592644ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (80.598114ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (651.842835ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.571622ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (5.380362ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (1.115207ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.693799ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.060569ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (7.078616ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.181245ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.833106ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.346432ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (28.150731ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (16.56649ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (8.789814ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (7.204342ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.880292ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (4.025895ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.141084ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.545979ms) +✔ 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.007947ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.314484ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.215368ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.479213ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.058811ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.207036ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.443608ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (7.736511ms) +✔ tools: listing and search caps hold (13.678244ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.959099ms) +✔ 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.77679ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.373321ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.317981ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.166706ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.209866ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.906211ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.477696ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.197139ms) +✔ 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.553116ms) +✔ 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.372419ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.266113ms) +✔ 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.705228ms) +✔ 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.225191ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3198.419333 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:201:1 +✖ a restart after the second refusal does not send before that refusal's 30 min are up (63.495042ms) + AssertionError [ERR_ASSERTION]: nothing between the restart and +60 min + + actual - expected + + [ + + 31.5 + - 60 + ] + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:208: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: [ 31.5 ], + expected: [ 60 ], + operator: 'deepStrictEqual', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-X14.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-X14.txt new file mode 100644 index 00000000..20ef112e --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-X14.txt @@ -0,0 +1,286 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (36.439821ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (33.925039ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (21.158075ms) +✔ decide prints a declining choice as declining (18.952008ms) +✔ an unknown outcome is reported once and never resent (16.952476ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (20.117146ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (16.377237ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (17.785936ms) +✔ every human command refuses inside an agent run before it touches the bus (16.701711ms) +✔ usage errors exit 4; no business and no host is a usage error (17.493924ms) +✔ agents and tasks print through the broker (17.802635ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (3.969558ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (65.153084ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (65.019891ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (63.315796ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (57.643913ms) +✔ a business without tracker.baseUrl gets no trackers entry (79.072851ms) +✔ an unknown business and a broken system config refuse with exit 3 (100.614075ms) +✔ empty views say so (1.321977ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.642474ms) +✔ tasks print the tracker fields the snapshot carries (0.24347ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (979.002116ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (149.546048ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (75.917239ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.077629ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (125.479723ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (74.096919ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (128.383414ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (75.365589ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (149.996979ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (24.844573ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.886459ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (209.271349ms) +✔ bus start refuses with exit 3 without a notifier config (89.833801ms) +✔ bus start runs until bus stop; status reports it while it runs (596.240557ms) +✖ bus-service.sh renders the unit and installs it into a given directory (13.023386ms) +✔ zoned uses the IANA zone across DST (25.081139ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (33.087017ms) +✔ two blocking decisions get two DMs with different nonces (21.832137ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.313202ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (20.727213ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (26.646629ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (26.996695ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (17.003301ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (154.842007ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (73.169397ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (20.197995ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (12.140882ms) +✔ an inbox read failure is logged and the next poll retries (0.826196ms) +✔ no Discord id reaches the journal or the log (12.640011ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.577376ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.291009ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.274937ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.572193ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.647559ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (3.528525ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.371869ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.540285ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.444668ms) +✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.517872ms) +✔ digest content stays within Discord's 2000 characters (0.282252ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.600047ms) +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.436455ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (64.122569ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2279.073027ms) +✔ busExit and refuseInsideAgent (0.386801ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (5.051726ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.451345ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.679177ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.505543ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.759513ms) +✔ 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.847581ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.885012ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.733518ms) +✔ authorize: open channel, listed user (2.103689ms) +✔ authorize: wrong guild (0.204134ms) +✔ authorize: no guild (DM) (0.250642ms) +✔ authorize: unlisted channel (0.179972ms) +✔ authorize: unknown channel, no info (0.260484ms) +✔ authorize: thread of listed parent (0.444085ms) +✔ authorize: thread of unlisted parent (0.296317ms) +✔ authorize: text channel that is not a thread and not listed (0.141873ms) +✔ authorize: unlisted user (1.233958ms) +✔ authorize: no author (0.509405ms) +✔ authorize: bot author (listed id, bot flag) (0.144304ms) +✔ authorize: system author (0.325929ms) +✔ authorize: the bot itself (0.098491ms) +✔ authorize: webhook (0.66558ms) +✔ authorize: mention channel without mention (0.157236ms) +✔ authorize: mention channel with bot mention (0.138165ms) +✔ authorize: mention channel with @everyone only (0.089143ms) +✔ authorize: mention channel mentioning someone else (0.085157ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.083237ms) +✔ authorize: private thread under mention channel, mentioned (0.094689ms) +✔ authorize: private thread under mention channel, not mentioned (0.266387ms) +✔ authorize: thread in another guild per channel info (0.083028ms) +✔ authorize: not an object (0.07959ms) +✔ authorize: no id (0.070516ms) +✔ authorize: oversize content is accepted and flagged (0.084489ms) +✔ authorize: exactly the limit is not oversize (0.068505ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.465465ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.138041ms) +✔ binding: a complete binding validates and is frozen (2.574002ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.281521ms) +✔ binding: empty allowlists refuse (0.325095ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.384415ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.623987ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.200867ms) +✔ binding: file must be 0600, regular, not a symlink (1.649798ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.365167ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (112.716415ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.641304ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (435.544546ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (202.966592ms) +✔ cli: run refuses when STOP is present, before any network use (151.624562ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.8529ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.386448ms) +✔ 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.641577ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.801329ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.489508ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.553887ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.715878ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.415618ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.013139ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.915224ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.294216ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.052537ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (44.144941ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (33.552931ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.922673ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (7.891341ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.13703ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (5.04555ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (3.47815ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.089163ms) +✔ 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 (8.646223ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (3.375862ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.101761ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (4.56979ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (8.744825ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.499542ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.776987ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (1.883161ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.241348ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.405812ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (0.999705ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.576183ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.819668ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.461278ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (55.109589ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (50.2841ms) +✔ engine: one prompt, one turn, text and usage come back (48.874702ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (358.597213ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (234.327562ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (115.980983ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (232.999465ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (127.109157ms) +✔ 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.630669ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.344473ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.354236ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.500428ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (425.782047ms) +✔ engine: a malformed JSONL line fails the turn, not the process (24.521955ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.954091ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.575865ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (1.640094ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.494401ms) +✔ gateway: op 9 resumable resumes (0.680377ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.950533ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.5513ms) +✔ gateway: close() is final and unparseable frames are ignored (0.412734ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (79.181806ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (67.333024ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (163.8268ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.339034ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (96.931881ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (86.40052ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (89.861594ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (213.438671ms) +✔ git: push pushes the named branch only and reports up to date (79.995959ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (91.684346ms) +✔ git: the credential helper answers get over https from a private file and nothing else (198.939395ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (760.532022ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (4.518779ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (94.136648ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.321714ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.010352ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (84.932333ms) +✔ 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 (371.359177ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.953502ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (25.87851ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.08047ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (42.520075ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (162.423161ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (90.616142ms) +✔ notices: a kind is recorded per UTC day and found again (0.478887ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.466472ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.244007ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.967992ms) +✔ 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.135659ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (68.081432ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (52.742578ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (92.661558ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (685.712976ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.555387ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (4.983774ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.512018ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.572608ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.682831ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.690252ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.4364ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.873619ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.484239ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.401741ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.780187ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.506372ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (4.523326ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.868572ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.473946ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1017.080749ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.417568ms) +✔ 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.467204ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.171844ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.198172ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.465489ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.187242ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (4.395016ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (6.590029ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.883006ms) +✔ tools: listing and search caps hold (13.764368ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.895509ms) +✔ 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.850724ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.464872ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.848609ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.002398ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.244872ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.849368ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.865861ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.229826ms) +✔ web: web_fetch refuses bad urls, private hosts, rebinding names, non-https redirects, too many hops, error status, non-text bodies, and times out (1032.551897ms) +✔ 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.2694ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.235987ms) +✔ 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.621124ms) +✔ 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.199604ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3152.644118 + +✖ failing tests: + +test at packages/cli/tests/host.test.mjs:396:1 +✖ bus-service.sh renders the unit and installs it into a given directory (13.023386ms) + AssertionError [ERR_ASSERTION]: The input did not match the regular expression /^ {2}mkdir -m 0700 -p \/notify\/ +the notifier refuses a looser directory$/m. Input: + + 'written: /mnt/storage/scratch/tmp/mosaic-cli-L14dvV/mosaic-bus@.service\n' + + 'next, for one business (one per data root):\n' + + ' write /notify//notify.json, mode 0600:\n' + + ' {"notifyVersion": 1, "binding": ""} or "binding": null for no DMs\n' + + ' systemctl --user enable --now mosaic-bus@ start now and at login\n' + + ' systemctl --user status mosaic-bus@\n' + + " journalctl --user -u mosaic-bus@ -f the host's log\n" + + ' systemctl --user stop mosaic-bus@ SIGTERM; restartable\n' + + 'survive logout and reboot only with lingering on: loginctl enable-linger jwoltje\n' + + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/host.test.mjs:409: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 process.processTicksAndRejections (node:internal/process/task_queues:104:5) + at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: `written: /mnt/storage/scratch/tmp/mosaic-cli-L14dvV/mosaic-bus@.service\nnext, for one business (one per data root):\n write /notify//notify.json, mode 0600:\n {"notifyVersion": 1, "binding": ""} or "binding": null for no DMs\n systemctl --user enable --now mosaic-bus@ start now and at login\n systemctl --user status mosaic-bus@\n journalctl --user -u mosaic-bus@ -f the host's log\n systemctl --user stop mosaic-bus@ SIGTERM; restartable\nsurvive logout and reboot only with lingering on: loginctl enable-linger jwoltje\n`, + expected: /^ {2}mkdir -m 0700 -p \/notify\/ +the notifier refuses a looser directory$/m, + operator: 'match', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-X9.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-X9.txt new file mode 100644 index 00000000..45443464 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-X9.txt @@ -0,0 +1,287 @@ +✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (42.695821ms) +✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (50.900968ms) +✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (19.526315ms) +✔ decide prints a declining choice as declining (27.0715ms) +✔ an unknown outcome is reported once and never resent (17.942055ms) +✔ a decision closed before the answer arrives exits 2 and points at its trail (21.456887ms) +✔ a prefix that matches two open decisions exits 2 and resolves neither (20.232816ms) +✔ without --business a command uses the live host's business, and a stale host.json is not a host (28.167704ms) +✔ every human command refuses inside an agent run before it touches the bus (23.232404ms) +✔ usage errors exit 4; no business and no host is a usage error (21.716825ms) +✔ agents and tasks print through the broker (26.685493ms) +✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (2.448765ms) +✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (101.758457ms) +✔ trackers come from the tracker.* variables of the one project that names a tracker project (64.589268ms) +✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (60.806348ms) +✔ two projects that each name a tracker project refuse, since the boot shape holds one (64.399373ms) +✔ a business without tracker.baseUrl gets no trackers entry (75.658057ms) +✔ an unknown business and a broken system config refuse with exit 3 (94.792104ms) +✔ empty views say so (1.146799ms) +✔ the trail keeps the broker's order and names a decision's task without its rows (1.41943ms) +✔ tasks print the tracker fields the snapshot carries (0.361908ms) +✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (1019.227828ms) +✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (152.084167ms) +✔ a second host for the same data root refuses with exit 3 while the first runs (79.194062ms) +✔ a notifier that refuses stops the broker and the host refuses with exit 3 (128.995735ms) +✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (127.01056ms) +✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (76.219523ms) +✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (122.025954ms) +✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (72.468138ms) +✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (138.063567ms) +✔ watchChildren reports a child that died before it was called, and one that dies later (23.990777ms) +✔ bus stop refuses to signal a live pid that is not a bus host (202.881279ms) +✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (206.23692ms) +✔ bus start refuses with exit 3 without a notifier config (83.701998ms) +✔ bus start runs until bus stop; status reports it while it runs (594.58956ms) +✔ bus-service.sh renders the unit and installs it into a given directory (27.364129ms) +✔ zoned uses the IANA zone across DST (26.624668ms) +✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (47.126452ms) +✔ two blocking decisions get two DMs with different nonces (35.118213ms) +✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.335013ms) +✔ a failed DM is journaled, backs off, and is retried until it lands (31.814163ms) +✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (31.270483ms) +✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (29.440396ms) +✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (23.556561ms) +✔ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then (170.013966ms) +✔ a restart after the second refusal does not send before that refusal's 30 min are up (56.029087ms) +✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (15.519714ms) +✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (9.167494ms) +✔ an inbox read failure is logged and the next poll retries (0.705928ms) +✔ no Discord id reaches the journal or the log (10.038691ms) +✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (2.377141ms) +✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (2.279316ms) +✔ the journal: a whole file that is one torn line truncates to empty (1.191204ms) +✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.485391ms) +✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.550256ms) +✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.966907ms) +✔ the journal: a symlinked directory refuses and says it is a link (0.567178ms) +✔ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3 (0.476903ms) +✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.377081ms) +✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path (1.505115ms) +✔ digest content stays within Discord's 2000 characters (0.457248ms) +✔ runLoop never overlaps ticks and stops after the one in flight (111.351089ms) +task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 +task.close on a missing task answered: task-not-found; it made GET /tasks/999 404 +✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (406.719655ms) +✔ the transport writes {business, verb, args} to the child and reads its JSON (82.627037ms) +✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2314.22678ms) +✔ busExit and refuseInsideAgent (0.387445ms) +✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (4.949848ms) +✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.72578ms) +✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.627445ms) +✔ approvals: a button approves only on its own request message with the matching custom id (0.547957ms) +✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (32.452945ms) +✔ 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.780424ms) +✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (15.756427ms) +✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.716303ms) +✔ authorize: open channel, listed user (2.116809ms) +✔ authorize: wrong guild (0.218948ms) +✔ authorize: no guild (DM) (0.178971ms) +✔ authorize: unlisted channel (0.220612ms) +✔ authorize: unknown channel, no info (0.226098ms) +✔ authorize: thread of listed parent (0.216643ms) +✔ authorize: thread of unlisted parent (0.206414ms) +✔ authorize: text channel that is not a thread and not listed (0.129743ms) +✔ authorize: unlisted user (0.399689ms) +✔ authorize: no author (0.257888ms) +✔ authorize: bot author (listed id, bot flag) (0.158293ms) +✔ authorize: system author (0.113693ms) +✔ authorize: the bot itself (0.105945ms) +✔ authorize: webhook (0.088276ms) +✔ authorize: mention channel without mention (0.133082ms) +✔ authorize: mention channel with bot mention (0.171777ms) +✔ authorize: mention channel with @everyone only (0.118598ms) +✔ authorize: mention channel mentioning someone else (0.102448ms) +✔ authorize: mention channel, content says @bot but mentions empty (0.188459ms) +✔ authorize: private thread under mention channel, mentioned (0.108392ms) +✔ authorize: private thread under mention channel, not mentioned (0.086117ms) +✔ authorize: thread in another guild per channel info (0.095182ms) +✔ authorize: not an object (0.060444ms) +✔ authorize: no id (0.064663ms) +✔ authorize: oversize content is accepted and flagged (0.087816ms) +✔ authorize: exactly the limit is not oversize (0.065254ms) +✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (1.91215ms) +✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (1.146256ms) +✔ binding: a complete binding validates and is frozen (2.615019ms) +✔ binding: unknown key, missing field, wrong type refuse with exit 2 (1.337072ms) +✔ binding: empty allowlists refuse (0.895847ms) +✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (2.916197ms) +✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (4.098608ms) +✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (0.827967ms) +✔ binding: file must be 0600, regular, not a symlink (2.909621ms) +✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (2.370774ms) +✔ cli: check refuses a non-0600 token file with exit 2 before any network use (131.540131ms) +✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (3.987938ms) +✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (416.545817ms) +✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (204.124078ms) +✔ cli: run refuses when STOP is present, before any network use (172.458112ms) +✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (2.076345ms) +✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.564452ms) +✔ 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.99628ms) +✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (20.642317ms) +✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.767332ms) +✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.357443ms) +✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.610001ms) +✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (3.67124ms) +✔ turn: a second message during a turn is held by the engine, both get their own reply and record (34.36697ms) +✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.821187ms) +✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.667123ms) +✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.204315ms) +✔ restart: an inbox with three ids and a replay of the same three produces zero turns (44.147074ms) +✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.723872ms) +✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (7.999848ms) +✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (4.400002ms) +✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (3.421125ms) +✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (6.035732ms) +✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (4.577778ms) +✔ journal: no token-shaped string and no model output on the drop path reaches disk (1.049663ms) +✔ 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.606455ms) +✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.945793ms) +✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (5.902214ms) +✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (1.995553ms) +✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (5.633274ms) +✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (8.128078ms) +✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (1.086506ms) +✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.245325ms) +✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.225153ms) +✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (2.571003ms) +✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.986458ms) +✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.730167ms) +✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.59808ms) +✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.477007ms) +✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (70.434554ms) +✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (59.656196ms) +✔ engine: one prompt, one turn, text and usage come back (54.286738ms) +✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (378.18188ms) +✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.133027ms) +✔ engine: timeout sends abort and fails only that turn; the process stays (105.242619ms) +✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (236.697114ms) +✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (124.481269ms) +✔ 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.036359ms) +✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.793151ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.392011ms) +✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.514755ms) +✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (426.246807ms) +✔ engine: a malformed JSONL line fails the turn, not the process (25.181641ms) +✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (45.58512ms) +✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (4.210163ms) +✔ gateway: missed ack closes the socket and resumes with the last sequence (2.021905ms) +✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.565785ms) +✔ gateway: op 9 resumable resumes (0.947653ms) +✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (1.704494ms) +✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.58659ms) +✔ gateway: close() is final and unparseable frames are ignored (0.970406ms) +✔ git: config validation is strict, needs write: true, a work tree and a private token file (79.348559ms) +✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (63.850933ms) +✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (162.955784ms) +✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.305959ms) +✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (86.971312ms) +✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (94.214462ms) +✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (90.400645ms) +✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (235.813734ms) +✔ git: push pushes the named branch only and reports up to date (73.935437ms) +✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (96.956611ms) +✔ git: the credential helper answers get over https from a private file and nothing else (198.660681ms) +✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (752.56004ms) +✔ lock: the claim is exclusive; a second start against a live owner refuses (6.081677ms) +✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (108.168434ms) +✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.226863ms) +✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (5.647633ms) +✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (76.813084ms) +✔ lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it (342.479082ms) +✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.380568ms) +✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.340548ms) +✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (0.986595ms) +✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (46.034157ms) +✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (181.634629ms) +✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (93.79815ms) +✔ notices: a kind is recorded per UTC day and found again (0.419578ms) +✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.511894ms) +✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.573585ms) +✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.750235ms) +✔ recover: nothing to do is clean; a lock whose owner is gone or that has no record is cleared and STOP ends up absent (52.481055ms) +✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (61.120525ms) +✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (59.098891ms) +✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (86.863926ms) +✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (716.13454ms) +✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.642214ms) +✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.228517ms) +✔ rest: content and nonce limits are enforced locally; typing never throws (0.589473ms) +✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.660656ms) +✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (0.849316ms) +✔ setspark config: a bare https or loopback origin, a private key file, a principal (7.93533ms) +✔ setspark config: reaches the tools config and the binding as a fixed key (3.559885ms) +✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.871351ms) +✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (3.039388ms) +✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (31.865221ms) +✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (13.500828ms) +✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (9.361846ms) +✔ setspark keys: read per call, one printable token per file, rotation without a restart (5.723068ms) +✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.617638ms) +✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (3.405558ms) +✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1013.946075ms) +✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.473246ms) +✔ 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.532815ms) +✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.239307ms) +✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.20801ms) +✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.714297ms) +✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.036644ms) +✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (6.208638ms) +✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (7.991555ms) +✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.727937ms) +✔ tools: listing and search caps hold (11.975375ms) +✔ tools: credential shapes are caught; ordinary prose and ids are not (0.948319ms) +✔ 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.128059ms) +✔ tools: an unreadable file under the root is skipped by search and refused by read (1.324429ms) +✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.347562ms) +✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (5.563725ms) +✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (3.24243ms) +✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.660983ms) +✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.329986ms) +✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (1.890631ms) +✔ 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.336229ms) +✔ 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.273426ms) +✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.244886ms) +✔ 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.652888ms) +✔ 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.40933ms) +ℹ tests 244 +ℹ suites 0 +ℹ pass 243 +ℹ fail 1 +ℹ cancelled 0 +ℹ skipped 0 +ℹ todo 0 +ℹ duration_ms 3187.126372 + +✖ failing tests: + +test at packages/cli/tests/notifier.test.mjs:410:1 +✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path (1.505115ms) + AssertionError [ERR_ASSERTION]: The validation function is expected to return "true". Received false + + Caught error: + + Error: EACCES: permission denied, open '/mnt/storage/scratch/tmp/mosaic-cli-tXUsQU/notify/demo/sent.jsonl' + at TestContext. (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:423: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: Error: EACCES: permission denied, open '/mnt/storage/scratch/tmp/mosaic-cli-tXUsQU/notify/demo/sent.jsonl' + at openSync (node:fs:779:18) + at openJournal (file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/src/notifier.mjs:182:10) + at file:///mnt/storage/scratch/rocko-r45/tree/packages/cli/tests/notifier.test.mjs:423: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:423: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), + operator: 'throws', + diff: 'simple' + } diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-end.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-end.txt new file mode 100644 index 00000000..8bad4e87 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-end.txt @@ -0,0 +1,2 @@ +3.85 2.71 2.46 3/12539 879606 +2026-10-09T14:08:50Z diff --git a/agents/rocko/work/s4-follow-up/out/r2/mut-start.txt b/agents/rocko/work/s4-follow-up/out/r2/mut-start.txt new file mode 100644 index 00000000..1068488b --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mut-start.txt @@ -0,0 +1,2 @@ +2.44 1.79 2.16 2/12979 779337 +2026-10-09T14:05:56Z diff --git a/agents/rocko/work/s4-follow-up/out/r2/mutants.txt b/agents/rocko/work/s4-follow-up/out/r2/mutants.txt new file mode 100644 index 00000000..6a9527fd --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/r2/mutants.txt @@ -0,0 +1,31 @@ +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 2, cancelled 0) ✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count;✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then; +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 2, cancelled 0) ✖ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count;✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then; +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; +G144cb killed (fail 1, cancelled 0) ✖ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error; +G150cb killed (fail 1, cancelled 0) ✖ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3; +G184 killed (fail 1, cancelled 0) ✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1; +G188 killed (fail 1, cancelled 0) ✖ close() whose stop and close sends fail with EPIPE still finishes, with exit 1; +R2a killed (fail 2, cancelled 0) ✖ a failed DM is journaled, backs off, and is retried until it lands;✖ polled every POLL_MS against a permanent 403, a DM is sent at 0, 30, 60, 90 and 120 min and gives up only then; +R2b killed (fail 1, cancelled 0) ✖ a restart after the second refusal does not send before that refusal's 30 min are up; +R2c killed (fail 1, cancelled 0) ✖ a restart after the second refusal does not send before that refusal's 30 min are up; +X9 killed (fail 1, cancelled 0) ✖ the journal: a directory it cannot write or create refuses with exit 3 and names the path; +X14 killed (fail 1, cancelled 0) ✖ bus-service.sh renders the unit and installs it into a given directory; +D3 killed (fail 1, cancelled 0) ✖ the journal: a line with a wrong type refuses with exit 3 and names the field; +N1a killed (fail 1, cancelled 0) ✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3; +N1b killed (fail 1, cancelled 0) ✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3; +N1c killed (fail 1, cancelled 0) ✖ the journal: a dangling directory link, a parent that is a file and a journal that is a directory each refuse with exit 3; diff --git a/agents/rocko/work/s4-follow-up/out/suite-auth-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-auth-r2.txt new file mode 100644 index 00000000..ac228760 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-auth-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-conductor-r2.txt new file mode 100644 index 00000000..62afc513 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-conductor-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-config-r2.txt new file mode 100644 index 00000000..76c05ef7 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-config-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-discord-r2.txt new file mode 100644 index 00000000..b013f9ff --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-discord-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-extension-package-r2.txt new file mode 100644 index 00000000..a2c28f29 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-extension-package-r2.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: 745170 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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-foundation-r2.txt new file mode 100644 index 00000000..6338e1e6 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-foundation-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-queue-r2.txt new file mode 100644 index 00000000..cdc93566 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-queue-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-release-r2.txt new file mode 100644 index 00000000..1d6ed050 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-release-r2.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-r2.txt b/agents/rocko/work/s4-follow-up/out/suite-task-r2.txt new file mode 100644 index 00000000..85aa46f0 --- /dev/null +++ b/agents/rocko/work/s4-follow-up/out/suite-task-r2.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 index 5c793648..ab96d642 100644 --- a/agents/rocko/work/s4-follow-up/packet-manifest.sha256 +++ b/agents/rocko/work/s4-follow-up/packet-manifest.sha256 @@ -1,16 +1,20 @@ -fd7c9a28364d61669889180bce1482449ab0a583228ebb9e0e8fe5075cbe5eb0 BUILD.md -c88b20e4bd185682c82a4ce1733cd5b5a23a5375a10e9de55f2af5f8af7474a9 base.txt -4ed9ff61b94b4c9d2426e5703c2e6c0dbcd6bc0c64a88d59347a8b47f4933408 build.patch -b329fdcbdf8cb62659f239359570dfa9771559e3112b743df55429244769ed14 candidate-manifest.sha256 +ff1af113dc9e41699cd697b39cc2f66a89b8cf3febbede8fb87365faa4cd4fb9 BUILD.md +b084be74fff27adc1b15158404edbbfee3efe3ac5a2288efa63ebc1a76bebd0d base.txt +c8cec070da60d8297f1ee5b006a1099ab9376fd3abf32fa4461f967750ad6756 build.patch +5b067a9dad645c31d99e1ea02575cd2fc1ba547ce011ea81c56b17ae29da0d0e candidate-manifest.sha256 8d35f99580981064d08725cf8e3dd67885bff77603446b0022701d4357727273 files.txt -bbe45c2a578b35cc2559972a4ee8900bd01ffb69e72bef6472dfc7e147e8916f mutants.sh +ecf3b48d64d2c73dda8fff357aabc9e8e719920a517d7172a02fce3c240d3756 mutants.sh 1102fd4f14df908f0498d31d209023d7903ba090c6c3bf3e3338e3f52158d95d out/base-node-conversation.txt 8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/base-suite-task.txt +795771fa8a721586343c05dac647306752fad14e06d738bad72af5bd388e3d59 out/gate-r2-end.txt +70f96042dfa6380b6d505a50401ecf16ad87f8daf653a5b295bd43fde6d753fd out/gate-r2-exits.txt +3a0e4de0a31a7fc5ecff3979aadfd495d0c12599e361323aca6101d78a964522 out/gate-r2-start.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-r2.txt e1bd7a5920b70115d2251ae3289799bb446445703b9da51c55c668f9cedfa461 out/manifest-check-tree.txt 95bdcddfb56d633c72c0b8c18948cffd42e6c1b2aaa3b78800783eb2d1452e35 out/mut-E1.txt 1e13a94767e411e0eccf09d8b362dba095e33b6403f3d9bc5cde3e7cdfe62315 out/mut-E2.txt @@ -31,26 +35,82 @@ e75b166d3aa8f605d394ed5449c20a11ed884c910c756230697d4f387e13cd50 out/mut-J4a.tx a6dc80efac48f9de98748433fcac165b4cc3bf000de2b4c04d9107c9b3dfd342 out/mut-N4.txt a0df618c85c05350971ed56203a4d8f37e8a6cc0fa2ba267de0ba34fd197523f out/mut-N5.txt 1225000966c8c8c2231ff7091665950f17a6d9d756a46f48b4ae3a8400aa635d out/mutants.txt +e0476e93931dc2fea8ce2d77174ce3ddedc30592281d4f0234c1d2e12049e693 out/node-bus-r2.txt f14fe1ee4239ae63dfb1bea143d1d450905387a34abbcb9e4c6b09104b75aefa out/node-bus.txt +cc54ca08e481c76ebe389fd8e4686c7f1cd912a7cecb017adfaf91e23d040354 out/node-business-r2.txt 1a88ea66c4b4e6cbf23a420628b53082a770828fb10ddf3ae49cd5b65f4855d0 out/node-business.txt +4bf6f80ff71513010f9e8bcb17924ca7d346ea7f0fe0362404a0c94a4cc35e80 out/node-cli-r2.txt 099d2019d935d5c49cecff107316b040a93959ea832375cc008cb8c991004174 out/node-cli.txt +8e1a41b889b71cdcaee93c3ed4bc14e9c065048f750488069343b82e1178e7bb out/node-control-board-r2.txt 9d2ef4cf44c714e9d18a2162b1c34aad8867ca631d66253cf08c11c96dd6f472 out/node-control-board.txt +254a01cfe6a9516172de1368f6d701e5cfb42929174b9ed3424196666eadac7f out/node-conversation-r2.txt bb4541854d7f2ced39d032f84f36088be1961ec7cf7c591c1196c94157c61858 out/node-conversation.txt +6db90f6b20cf0b50eca8d95b4488f999b4f53ba044bacc47a054c15c372e691a out/node-discord-r2.txt 9643c28e1490621836a1c64e2b62fad8f44033965cfc68df49187ff5e7c62c33 out/node-discord.txt +609bc17c7693d64f49120761b7f60712fbce191f5bbdb26fbb1e56b201f4477f out/node-ledger-r2.txt c5fdadd964fdccc88182f91fa2b724ec7b69d1246b972c25536cc398800127dd out/node-ledger.txt +01bbb89694e16d8b0af8f6da010b931fbf202ae73a97b220afbf675d849cc4e3 out/node-mosaic-r2.txt fc0d61d1a3695c44bc5e698523bd790e68bfde3785f38526c601bee852ad3784 out/node-mosaic.txt +8b86072a96cc9526d57cbb04f2812fa1b0b884272439538788154d42f6393169 out/node-queue-r2.txt e2e08d74f8cb590e75d50bc011ca5875d8d49c1470e7f9ee9173cba39946eec9 out/node-queue.txt +33da17d4998d61f2628648ed5ab04ddd8aff65e231267676ae983a2eac45814c out/node-seat-r2.txt 74dbaa3099f9e3e88219cdc2ae4b83f21fc18ddafe060b299c1dcc5eacf95528 out/node-seat.txt +cdef1d30ea8e68e2701bc950d233a0f36fdd41613306f55198b1a3822bd72d2c out/node-tasks-r2.txt 34e473be5528b430fb28d9e78b59092e4bedf8660306590b24b6083ca844a697 out/node-tasks.txt +2f2e8dd3922e2d6c737ae636e0eefc02678aaf0d0e37e17f0c5b15cb007ccadf out/node-webui-r2.txt a911209250eef9dbdbb7c762655b29811b42198cf439c363c601993ac30e1a3c out/node-webui.txt +e1bd7a5920b70115d2251ae3289799bb446445703b9da51c55c668f9cedfa461 out/r2/manifest-check-mut.txt +71bc7a5b9376362707c79fa0c7e0037058d22f54de5394f448e390dc6e2faa4a out/r2/mut-D3.txt +b3dea3c963be86ee51c91282cea5540bff7ab49f4f8031d99c089aae7e47d628 out/r2/mut-E1.txt +10f2e9db7f01fa8940bb9eaa25caac515efc31b0d4097585818eacb5b7898f87 out/r2/mut-E2.txt +4abaeec9660967e5e3ffa15bcfccdda878614ad6b11ee3884626d6a7f9aee985 out/r2/mut-F2a.txt +1e3f85e428b630abc810a76843f1ca9bb9ad10c357553892c764782a7d591b1d out/r2/mut-F2b.txt +891f2876e90ca1feceb284f7dbceb47c4f6dc9048acca4eaa0eaa579a1eeaada out/r2/mut-F2c.txt +c4f702a00f877a1ab60001e76ae88af54c1f54d4b38b9bfe8d2332516f108d47 out/r2/mut-F2d.txt +eb114215eca32c40514f3bae0cc060e19364d41507da306f25b498a01ae09a85 out/r2/mut-F2e.txt +ae7a3521ae600758fa00561ee94f666491ea334accf6c587b536a58a4db81392 out/r2/mut-F2f.txt +2e347ed278b09249815aff36c2d6918dac66e5abb1be5472c3c2296e63d9d28c out/r2/mut-G144.txt +9e17773ef5226ee5be34b779a5fbd2836a6afb40dd46d3835e7ff9f47330f351 out/r2/mut-G144cb.txt +ca18292aebbb750e23f6868f83af114042939ea027e9bfb218217733bf221d50 out/r2/mut-G150.txt +6a33017f5886c876e1294a7edba9d2dc77632bc1c743ce617be11476d7d3d9a7 out/r2/mut-G150cb.txt +44012b9779bfec8d8eeb5b63a8cf0779d40c807e0549df0154254f8a70f39412 out/r2/mut-G184.txt +0231a1718a60e7113b00a85d4283204a9f141a0261f28340e804defa77aee97a out/r2/mut-G188.txt +c841dab84d16c56f9ddeb6a8b6596d470a95560960a0c244857069bd41f254b0 out/r2/mut-J4a.txt +7bdb2636cc19b882475c537359eb4c491746938eb8ac77e6c973755ad810ae2f out/r2/mut-J4b.txt +e0b821077d8f5ac301d2a9575ad74c9ec3cd8c5024c8a58963bcf5d4076280f2 out/r2/mut-J4c.txt +ad759eeb23e69bb390b309efc8e753383de2a6183a9468c6d75bad5e6e292b2f out/r2/mut-J4d.txt +742e9d4485c22a7e87c1f52baccf4eba0f7d3191ec43e8b4f19533b41b757503 out/r2/mut-M28.txt +8c6e9dcdf5205c8cf5955cd4c54dbe858d72184396ba4a9a8eda479096900d58 out/r2/mut-N1a.txt +4600580bfd983aaff48d3b052f70e3822e5f47855b141d83d5122479a7d73d86 out/r2/mut-N1b.txt +9649c89a838906e57644719fa3a9ce7b4d476ce32f1b7d60a69f728f17d44198 out/r2/mut-N1c.txt +5eee699126b0efa5cd73d2b7ab898a28f01eea27d9258feb6ee92da1ad4ec594 out/r2/mut-N2.txt +0ec34dae8a5ce26d319a57af50c188451fcac7790fefc06e46069ca293c8e5c5 out/r2/mut-N4.txt +c79e920c1af4c40cc78651c7f50e9d1934303b577798771513f241c7dbd7f228 out/r2/mut-N5.txt +c599f60479601056d702137aa632aa01689ec2b2fbc6c300bd7fa4c0478c14c9 out/r2/mut-R2a.txt +047ae41126b9f0fb47918bcf4c57e8d75c3be1cfae82ad4a04fbc5243c513f22 out/r2/mut-R2b.txt +1c808e2f92791046aa30f12f5cc5c38cfd7713b30196d056a9cbca3ffcb53bdb out/r2/mut-R2c.txt +b5dd327fef18a3d27a8b6a3235e5cda327fa4ba3323fc639220fe9b5e6fab0d1 out/r2/mut-X14.txt +44d018a5d7d6d3a9a87f6af426b8813f1f222a6c5ff14728e14f0a562fe3a677 out/r2/mut-X9.txt +d3683280d195b7476b72ff0481d1c874c3252b7da5fd75a7233bc78e282a0643 out/r2/mut-end.txt +174f543b5500166f6ff4a11330e8fb514148f9cf8a2a88479c9251daf6b552df out/r2/mut-start.txt +065fec22c61273a215108713edcace910193705697e91248b1783efbf4546c25 out/r2/mutants.txt 8eaa5212718b37938ab792b00210ad00c7b793259ae9e0befc6f7b0316c1881e out/run.txt +f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/suite-auth-r2.txt f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/suite-auth.txt +e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/suite-conductor-r2.txt e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/suite-conductor.txt +52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/suite-config-r2.txt 52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/suite-config.txt +7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/suite-discord-r2.txt 7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/suite-discord.txt +4f840b0038586f0227c7f03914fdcb98bf9271068e3830183a877e5ac427d0d0 out/suite-extension-package-r2.txt e4c762a4d4225b2f3eab0e37cba1d36126e67465557501196b91c2ffdd576e0e out/suite-extension-package.txt +83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/suite-foundation-r2.txt 83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/suite-foundation.txt +bf5db498b82546af2d78a218a1d6446da993de629f4819fffeecf690ef93a314 out/suite-queue-r2.txt bf5db498b82546af2d78a218a1d6446da993de629f4819fffeecf690ef93a314 out/suite-queue.txt +6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/suite-release-r2.txt 6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/suite-release.txt +8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/suite-task-r2.txt 8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/suite-task.txt 50ceb870e3f56cac0ea04c7f7995a5eb5ec4e66f9c09a92a9fc7705e1df896cf run.sh