docs(s6): row 41 round 4 candidate packet, R5 fix (filbert)

Candidate manifest 08a78972 (42 files). Gate at 0a4c8f13.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-09 22:30:16 -05:00
co-authored by Claude Opus 5.5
parent 7ab2f92d02
commit 55bff3b274
23 changed files with 1512 additions and 1187 deletions
+134 -12
View File
@@ -1,15 +1,127 @@
# Row 41 (#1523): slice 1 S6, candidate packet, round 3 # Row 41 (#1523): slice 1 S6, candidate packet, round 4
Author: Filbert. Reviewer: Darkwing. Brief: `docs/plans/2026-10-04_slice-1.md`, Author: Filbert. Reviewer: Darkwing. Brief: `docs/plans/2026-10-04_slice-1.md`,
section "Slice 1 S6", blob `72d11de2`. Plan: `PLAN.md` here (b6d2fe2b). section "Slice 1 S6", blob `72d11de2`. Plan: `PLAN.md` here (b6d2fe2b).
Rulings: lead decisions 77 and 78, Sage's round 2 ruling (fix R1, R2 Rulings: lead decisions 77 and 78, Sage's round 2 ruling (fix R1, R2
and R3) and round 3 ruling (fix R4). Base: `915e00e5` (`base.txt`). None of and R3), round 3 ruling (fix R4) and round 4 ruling (fix R5). Base:
the 42 files changed between the base and `66b9e082`, where the round 3 gate `915e00e5` (`base.txt`). None of the 42 files changed between the base and
ran. The `0a4c8f13`, where the round 4 gate ran. The candidate source is uncommitted. There are no pushes. No token, private
candidate source is uncommitted. There are no pushes. No token, private
binding or tracker host was read or written, and nothing touched the live binding or tracker host was read or written, and nothing touched the live
`mosaic-bus@mosaic-stack` unit or `~/.mosaic-dev/bus/`. `mosaic-bus@mosaic-stack` unit or `~/.mosaic-dev/bus/`.
## Round 4
Darkwing's round 3 asked for changes (#1523 comment 27032, rev 271, packet
`agents/darkwing/work/s6-review/review-r3.md` and `r3/`, 974da6ed). R4 is
verified fixed. Sage ruled: fix R5 in this round; leave R1 to R4 alone, no
other scope. Four files changed since round 3, and none is new:
- `packages/harness/src/gate.mjs`, `README.md`
- `packages/harness/tests/gate.test.mjs`, `pi-session.test.mjs`
No source outside `insideWorkspace` in the gate changed.
### R5: a relative path is resolved from the real path of Pi's cwd
Both adapters `cd "$MOSAIC_WORKSPACE"` before they start the harness, and
a process's cwd is the real path (`getcwd`). Pi 0.85.1 resolves a relative
path lexically against `process.cwd()` (`resolveToCwd`), so `..` climbs the
real path's parents. Round 3's `insideWorkspace` resolved a relative path
against the workspace as given. With the workspace or the dataRoot behind
a symlink, the two climb different directories. In Darkwing's layout
(`$R/data -> $R/deep/store`, workspace `$R/data/ws`), `../../data/ws/X` is
`$R/data/ws/X` to the gate and `$R/deep/data/ws/X` to Pi. A real Pi
session read a file there and wrote a new one.
The fix: a relative path must resolve inside from both the workspace's
real path, as Pi resolves it, and the path as given. An absolute path is
unchanged. `spellings()` already built from both bases (R4), so `read`'s
other-spelling check needed no change.
The given-path check is a choice, and it is stricter than Pi. A path that
is inside for Pi but climbs out of the given path is refused, for example
`../../store/ws/x` when the workspace is `$R/a/ws -> $R/deep/store/ws`. I
kept it so the gate stays fail-closed whatever directory it runs in, and
doesn't rest on the adapter's `cd` alone. The cost is that such a path is
refused even though it is safe. A session has no need for it: the plain
relative path or the absolute one works.
### Claude Code isn't affected
Claude Code makes a `file_path` absolute against its own cwd before the
`PreToolUse` hook runs. Its cwd is also the real path, so `claude-gate.mjs`
gets the path Claude Code will open, and the absolute branch checks it.
Darkwing's `claude-cwd-dotdot.sh` shows this: the hook's refusal names
`<R>/deep/data/ws/secret.txt`, not the relative name. My rerun against
round 4 gives the same (below). The new check covers a relative path from
Claude Code too, if a later version passes one through.
### Tests
- `gate.test.mjs`, "a relative path climbs from the workspace's real path,
in both harnesses". Two layouts, each in Pi and Claude Code:
- workspace behind a symlink: `<dir>/a/ws -> <dir>/deep/store/ws`;
- dataRoot behind a symlink: `<dir>/data -> <dir>/deep/store`, workspace
`<dir>/data/workspaces/b/i`, the launcher's shape.
In each, the `../..` escape is inside as given and lands on a planted
file outside for Pi (the test asserts both). Read and write of the escape
are refused. Read and write of `../ws/...` or `../i/...`, through the
symlinked root and back in, are allowed. A path that is inside for Pi
but outside as given is refused.
- `pi-session.test.mjs`, "pi: a relative path climbs from the real path of
a workspace behind a symlink", and the same for a dataRoot. A real Pi
0.85.1 session runs through the adapter and the mock API, in the
symlinked workspace:
- the escape read and write come back as gate errors;
- the outside file's text never reaches stdout;
- the outside directory holds only the planted secret, and no
`planted.txt` appears in the workspace;
- the inside read returns its content, and the inside write lands.
`session()` in that file gains an optional workspace placement for this;
the other tests keep `<dir>/ws`.
### Mutants
Each was made on `gate.mjs`, restored from a copy and checked with `cmp`.
The base passes `gate.test.mjs` and `pi-session.test.mjs`, 19/19.
| Mutant | Change | Result |
|---|---|---|
| given | the given path only (round 3) | 16/3: the gate test and both Pi sessions |
| real | the real path only | 18/1: the gate test's stricter case |
The first draft of the gate test let `real` survive. The stricter case was
added for it.
### Darkwing's probes against round 4
`r3/probe/pi-cwd-dotdot.sh` and `claude-cwd-dotdot.sh`, run unchanged with
`WT` set to the round 4 build, both modes each. Outputs are in
`out/probe-pi-cwd-dotdot.txt` and `out/probe-claude-cwd-dotdot.txt`.
| Probe | Mode | Result |
|---|---|---|
| Pi | read | gate refuses `../../data/ws/secret.txt`; the tool result is the refusal; the secret isn't read |
| Pi | write | gate refuses `../../data/ws/planted.txt`; nothing is written outside or in the workspace |
| Claude Code | read | the hook refuses `<R>/deep/data/ws/secret.txt` (absolute) |
| Claude Code | write | the hook refuses `<R>/deep/data/ws/planted.txt`; nothing is written |
### Darkwing's other round 3 notes
Per Sage's ruling, nothing else changed this round. These go to
follow-ups:
- Mw, Mx, My and Mz survive the spelling tests. Each flips some of
Darkwing's spell-edge cases from refused to allowed: lowercase `am`,
two apostrophes, a path below a respelled self-loop, and the
normalised forms (`@`, NBSP before AM, `file://`, `%27`).
- Darkwing's fix 2: realpath the workspace in the seat's `workspaceDir`
(`packages/seat/src/session.mjs:46`), so a symlinked dataRoot never
reaches the harness as a given path.
- The Ma leftover (`fake-adapter.mjs`) reproduced, and stays a follow-up.
## Round 3 ## Round 3
Darkwing's round 2 asked for changes (#1523 comment 27010, rev 259, packet Darkwing's round 2 asked for changes (#1523 comment 27010, rev 259, packet
@@ -285,7 +397,7 @@ How the new tests separate the mutants:
## Files (`files.txt`, 42) ## Files (`files.txt`, 42)
`build.patch` is `git diff --cached --binary 915e00e5` over those files, `build.patch` is `git diff --cached --binary 915e00e5` over those files,
+4873/−63. It applies cleanly to `66b9e082` with `git apply --index`, and +4998/−63. It applies cleanly to `0a4c8f13` with `git apply`, and
`sha256sum -c candidate-manifest.sha256` passes in that tree. `sha256sum -c candidate-manifest.sha256` passes in that tree.
| Area | Files | What | | Area | Files | What |
@@ -400,27 +512,31 @@ project extensions) and is untouched.
## Gate ## Gate
Run at `66b9e082` with this round's patch applied, in a detached worktree. Run at `0a4c8f13` with this round's patch applied, in a detached worktree.
`sha256sum -c candidate-manifest.sha256` passed there (42 OK). Suites ran `sha256sum -c candidate-manifest.sha256` passed there (42 OK). Suites ran
sequentially, each output in `out/` (`out/summary.txt`). `TMPDIR` was on sequentially, each output in `out/` (`out/summary.txt`). `TMPDIR` was on
the scratch disk and `DOCKER_HOST=unix:///nonexistent.sock`, so nothing the scratch disk and `DOCKER_HOST=unix:///nonexistent.sock`, so nothing
reached Docker. Round 1's outputs stay in this directory at `9b670e27`, reached Docker. Round 1's outputs stay in this directory at `9b670e27`,
and round 2's at `779e9780`. round 2's at `779e9780` and round 3's at `0f38236f`. Darkwing's R5 probe
outputs from round 4 are `out/probe-pi-cwd-dotdot.txt` and
`out/probe-claude-cwd-dotdot.txt`.
| Suite | Pass | Fail | | Suite | Pass | Fail |
|---|---|---| |---|---|---|
| node: bus, business, cli, control-board | 74, 60, 83, 124 | 0 | | node: bus, business, cli, control-board | 74, 60, 83, 124 | 0 |
| node: conversation, discord, harness, ledger | 152, 178, 53, 78 | 0 | | node: conversation, discord, harness, ledger | 161, 178, 56, 78 | 0 |
| node: mosaic, queue, seat, tasks, webui | 69, 148, 27, 51, 14 | 0 | | node: mosaic, queue, seat, tasks, webui | 69, 148, 27, 51, 22 | 0 |
| test-auth, conductor, config, discord | 15, 17, 24, 66 | 0 | | test-auth, conductor, config, discord | 15, 17, 24, 66 | 0 |
| test-extension-package, foundation, queue, release | 18, 44, 27, 4 | 0 | | test-extension-package, foundation, queue, release | 18, 44, 27, 4 | 0 |
| test-task | 26 | 2 | | test-task | 26 | 2 |
Against round 2, cli gains 1 test (Mr) and harness 3 (R4). Against round 3, harness gains 3 tests (R5). Conversation (161) and webui
(22) grew because row 40 landed (08b428ec); none of their files is in this
candidate.
The two `test-task` failures are "user recall run succeeds (exit 1)" and The two `test-task` failures are "user recall run succeeds (exit 1)" and
"recalled user name". That check runs a live worker and needs Docker. The "recalled user name". That check runs a live worker and needs Docker. The
unpatched base at `66b9e082` fails the same two (`out/base-test-task.txt`, unpatched base at `0a4c8f13` fails the same two (`out/base-test-task.txt`,
identical PASS/FAIL lines), so they come from the environment, not this identical PASS/FAIL lines), so they come from the environment, not this
candidate. No test process was left running after the gate. candidate. No test process was left running after the gate.
@@ -446,3 +562,9 @@ names one, and fixtures use 127.0.0.1 and example.test.
that test cleans up when it fails, isn't traced. that test cleans up when it fails, isn't traced.
- `packages/conversation` (row 40): a `shim.mjs` from a gate's - `packages/conversation` (row 40): a `shim.mjs` from a gate's
conversation suite outlived the run, and it ignored SIGTERM. conversation suite outlived the run, and it ignored SIGTERM.
- Darkwing's round 3 survivors Mw, Mx, My and Mz in the gate's spelling
check (lowercase `am`, two apostrophes, a path below a respelled
self-loop, the normalised forms). Each needs a test; Sage kept them out
of round 4.
- Realpath the workspace in the seat's `workspaceDir`
(`packages/seat/src/session.mjs:46`), Darkwing's round 3 fix 2.
+139 -14
View File
@@ -2234,10 +2234,10 @@ index 00000000..41432793
+}); +});
diff --git a/packages/harness/README.md b/packages/harness/README.md diff --git a/packages/harness/README.md b/packages/harness/README.md
new file mode 100644 new file mode 100644
index 00000000..aa705b77 index 00000000..7598a573
--- /dev/null --- /dev/null
+++ b/packages/harness/README.md +++ b/packages/harness/README.md
@@ -0,0 +1,193 @@ @@ -0,0 +1,206 @@
+# harness +# harness
+ +
+What a managed role session runs from and runs as (slice 1 S6, issue #1523): +What a managed role session runs from and runs as (slice 1 S6, issue #1523):
@@ -2323,6 +2323,19 @@ index 00000000..aa705b77
+directories first. Pi calls it from the extension, Claude Code from +directories first. Pi calls it from the extension, Claude Code from
+`claude-gate.mjs`. +`claude-gate.mjs`.
+ +
+A relative path is resolved the way Pi resolves it: against its working
+directory. Both adapters `cd` into the workspace, and a process's working
+directory is the real path, so `..` climbs the real path's parents. With
+the workspace or the dataRoot behind a symlink, those differ from the
+parents of the path as given, and `../../data/ws/x` can be inside as given
+but outside for Pi. The gate requires a relative path to be inside from
+both the real path and the path as given. The second check is stricter
+than Pi: a path that only climbs out and back in through the real path's
+parents is refused. That keeps the gate fail-closed whichever directory it
+runs in. Claude Code isn't affected the same way: it makes a path absolute
+against its own (real) working directory before the `PreToolUse` hook
+sees it, so the gate gets the path Claude Code will open.
+
+Pi's `read` doesn't always open the name it is given. When that name +Pi's `read` doesn't always open the name it is given. When that name
+doesn't exist, it tries other spellings of the whole resolved path: a +doesn't exist, it tries other spellings of the whole resolved path: a
+narrow no-break space (U+202F) before ` AM.` or ` PM.`, the NFD form, a +narrow no-break space (U+202F) before ` AM.` or ` PM.`, the NFD form, a
@@ -2646,10 +2659,10 @@ index 00000000..ecada305
+} +}
diff --git a/packages/harness/src/gate.mjs b/packages/harness/src/gate.mjs diff --git a/packages/harness/src/gate.mjs b/packages/harness/src/gate.mjs
new file mode 100644 new file mode 100644
index 00000000..eaddd5dd index 00000000..e68e8f92
--- /dev/null --- /dev/null
+++ b/packages/harness/src/gate.mjs +++ b/packages/harness/src/gate.mjs
@@ -0,0 +1,158 @@ @@ -0,0 +1,164 @@
+// The tool gate both harnesses share. decide(policy, tool, input) answers +// The tool gate both harnesses share. decide(policy, tool, input) answers
+// one tool call: the policy's built-in tools and its typed tools pass, +// one tool call: the policy's built-in tools and its typed tools pass,
+// everything else is blocked, and every path argument of a file tool must +// everything else is blocked, and every path argument of a file tool must
@@ -2720,9 +2733,15 @@ index 00000000..eaddd5dd
+ return target === root || target.startsWith(root + sep); + return target === root || target.startsWith(root + sep);
+} +}
+ +
+// A relative path is resolved the way the tool resolves it: against its
+// cwd. Both adapters cd into the workspace, and a process's cwd is the real
+// path, so `..` climbs the real path's parents, not those of a workspace or
+// dataRoot given through a symlink. It must be inside against the path as
+// given too, so the check doesn't rest on how the harness was started.
+export function insideWorkspace(workspace, p) { +export function insideWorkspace(workspace, p) {
+ const s = normalise(p); + const s = normalise(p);
+ return within(workspace, isAbsolute(s) ? resolve(s) : resolve(workspace, s)); + if (isAbsolute(s)) return within(workspace, resolve(s));
+ return within(workspace, resolve(realpathSync(workspace), s)) && within(workspace, resolve(workspace, s));
+} +}
+ +
+// Pi's read (dist/core/tools/path-utils.js resolveReadPathAsync) opens +// Pi's read (dist/core/tools/path-utils.js resolveReadPathAsync) opens
@@ -3913,15 +3932,15 @@ index 00000000..4024172d
+} +}
diff --git a/packages/harness/tests/gate.test.mjs b/packages/harness/tests/gate.test.mjs diff --git a/packages/harness/tests/gate.test.mjs b/packages/harness/tests/gate.test.mjs
new file mode 100644 new file mode 100644
index 00000000..116364a0 index 00000000..85862fcc
--- /dev/null --- /dev/null
+++ b/packages/harness/tests/gate.test.mjs +++ b/packages/harness/tests/gate.test.mjs
@@ -0,0 +1,186 @@ @@ -0,0 +1,233 @@
+import { test } from "node:test"; +import { test } from "node:test";
+import assert from "node:assert/strict"; +import assert from "node:assert/strict";
+import { mkdirSync, symlinkSync, writeFileSync } from "node:fs"; +import { mkdirSync, realpathSync, symlinkSync, writeFileSync } from "node:fs";
+import { homedir } from "node:os"; +import { homedir } from "node:os";
+import { join } from "node:path"; +import { join, resolve } from "node:path";
+import { pathToFileURL } from "node:url"; +import { pathToFileURL } from "node:url";
+import { claudeBuiltins, decide, insideWorkspace } from "../src/gate.mjs"; +import { claudeBuiltins, decide, insideWorkspace } from "../src/gate.mjs";
+import { scratch } from "./helpers.mjs"; +import { scratch } from "./helpers.mjs";
@@ -4077,6 +4096,53 @@ index 00000000..116364a0
+ blocked(decide(viaLink, "read", { path: "x.txt" }), /outside the workspace under another spelling/); + blocked(decide(viaLink, "read", { path: "x.txt" }), /outside the workspace under another spelling/);
+}); +});
+ +
+// Both adapters cd into the workspace, and the tool's cwd is its real path,
+// so `..` climbs the real path's parents. Through a symlinked workspace or
+// dataRoot those differ from the given path's.
+const CLIMBS = {
+ // <dir>/a/ws -> <dir>/deep/store/ws: up two is <dir> as given, <dir>/deep for pi.
+ workspace(dir) {
+ mkdirSync(join(dir, "deep", "store", "ws"), { recursive: true });
+ mkdirSync(join(dir, "a"));
+ symlinkSync(join(dir, "deep", "store", "ws"), join(dir, "a", "ws"));
+ return { workspace: join(dir, "a", "ws"), outside: join(dir, "deep", "a", "ws"), escape: "../../a/ws", stay: "../ws", strict: "../../store/ws" };
+ },
+ // dataRoot <dir>/data -> <dir>/deep/store, the workspace under it as the
+ // launcher builds it: up four is <dir> as given, <dir>/deep for pi.
+ dataRoot(dir) {
+ mkdirSync(join(dir, "deep", "store", "workspaces", "b", "i"), { recursive: true });
+ symlinkSync(join(dir, "deep", "store"), join(dir, "data"));
+ return { workspace: join(dir, "data", "workspaces", "b", "i"), outside: join(dir, "deep", "data", "workspaces", "b", "i"), escape: "../../../../data/workspaces/b/i", stay: "../i", strict: "../../../../store/workspaces/b/i" };
+ },
+};
+
+test("a relative path climbs from the workspace's real path, in both harnesses", (t) => {
+ for (const [name, build] of Object.entries(CLIMBS)) {
+ for (const harness of ["pi", "claude-code"]) {
+ const dir = scratch(t);
+ const { workspace, outside, escape, stay, strict } = build(dir);
+ writeFileSync(join(workspace, "a.txt"), "a");
+ mkdirSync(outside, { recursive: true });
+ writeFileSync(join(outside, "secret.txt"), "s");
+ const policy = { harness, workspace, tools: ["read", "write"], typed: [] };
+ const [read, write, field] = harness === "pi" ? ["read", "write", "path"] : ["Read", "Write", "file_path"];
+ const at = `${name}, ${harness}`;
+ // As given, the escape is inside; from the real path it is outside.
+ assert.equal(resolve(workspace, escape), workspace, at);
+ assert.equal(resolve(realpathSync(workspace), escape), outside, at);
+ blocked(decide(policy, read, { [field]: `${escape}/secret.txt` }), /outside the workspace/);
+ blocked(decide(policy, write, { [field]: `${escape}/planted.txt` }), /outside the workspace/);
+ // Climbing out and back in by the same name stays inside on both paths.
+ allowed(decide(policy, read, { [field]: `${stay}/a.txt` }));
+ allowed(decide(policy, write, { [field]: `${stay}/new.txt` }));
+ // The other way round, inside for pi but outside as given, is refused
+ // too: the gate doesn't rest on the cwd the harness started in.
+ assert.equal(resolve(realpathSync(workspace), strict), realpathSync(workspace), at);
+ blocked(decide(policy, read, { [field]: `${strict}/a.txt` }), /outside the workspace/);
+ }
+ }
+});
+
+test("claude path fields per tool", (t) => { +test("claude path fields per tool", (t) => {
+ const { workspace, policy } = setup(t, "claude-code", ["read", "write", "edit", "grep", "find"]); + const { workspace, policy } = setup(t, "claude-code", ["read", "write", "edit", "grep", "find"]);
+ allowed(decide(policy, "Read", { file_path: join(workspace, "a.txt") })); + allowed(decide(policy, "Read", { file_path: join(workspace, "a.txt") }));
@@ -4312,10 +4378,10 @@ index 00000000..72992dde
+}); +});
diff --git a/packages/harness/tests/pi-session.test.mjs b/packages/harness/tests/pi-session.test.mjs diff --git a/packages/harness/tests/pi-session.test.mjs b/packages/harness/tests/pi-session.test.mjs
new file mode 100644 new file mode 100644
index 00000000..d2a89aef index 00000000..32db2aa3
--- /dev/null --- /dev/null
+++ b/packages/harness/tests/pi-session.test.mjs +++ b/packages/harness/tests/pi-session.test.mjs
@@ -0,0 +1,172 @@ @@ -0,0 +1,231 @@
+// Real pi (the pinned CLI) through adapters/pi with the extension, against +// Real pi (the pinned CLI) through adapters/pi with the extension, against
+// the scripted Messages API. No real model, no network beyond 127.0.0.1. +// the scripted Messages API. No real model, no network beyond 127.0.0.1.
+ +
@@ -4330,11 +4396,11 @@ index 00000000..d2a89aef
+ +
+const ADAPTER = join(REPO, "adapters", "pi", "adapter.sh"); +const ADAPTER = join(REPO, "adapters", "pi", "adapter.sh");
+ +
+async function session(t, script) { +async function session(t, script, place = (dir) => join(dir, "ws")) {
+ const dir = scratch(t); + const dir = scratch(t);
+ const workspace = join(dir, "ws"); + const workspace = place(dir);
+ const agentDir = join(dir, "pi-agent"); + const agentDir = join(dir, "pi-agent");
+ mkdirSync(workspace); + mkdirSync(workspace, { recursive: true });
+ mkdirSync(agentDir); + mkdirSync(agentDir);
+ writeFileSync(join(workspace, "notes.txt"), "inside\n"); + writeFileSync(join(workspace, "notes.txt"), "inside\n");
+ writeFileSync(join(dir, "secret.txt"), "outside\n"); + writeFileSync(join(dir, "secret.txt"), "outside\n");
@@ -4471,6 +4537,65 @@ index 00000000..d2a89aef
+ assert.ok(existsSync(s.turnMarker)); + assert.ok(existsSync(s.turnMarker));
+}); +});
+ +
+// The adapter cds into the workspace, and pi resolves `..` from that real
+// path. Each layout: where the workspace is given, and where `escape` lands
+// for pi (as given, it is the workspace itself).
+const CLIMBS = {
+ workspace: {
+ place(dir) {
+ mkdirSync(join(dir, "deep", "store", "ws"), { recursive: true });
+ mkdirSync(join(dir, "a"));
+ symlinkSync(join(dir, "deep", "store", "ws"), join(dir, "a", "ws"));
+ return join(dir, "a", "ws");
+ },
+ outside: (dir) => join(dir, "deep", "a", "ws"),
+ escape: "../../a/ws",
+ stay: "../ws",
+ },
+ dataRoot: {
+ place(dir) {
+ mkdirSync(join(dir, "deep", "store", "workspaces", "b", "i"), { recursive: true });
+ symlinkSync(join(dir, "deep", "store"), join(dir, "data"));
+ return join(dir, "data", "workspaces", "b", "i");
+ },
+ outside: (dir) => join(dir, "deep", "data", "workspaces", "b", "i"),
+ escape: "../../../../data/workspaces/b/i",
+ stay: "../i",
+ },
+};
+
+for (const [name, { place, outside, escape, stay }] of Object.entries(CLIMBS)) {
+ test(`pi: a relative path climbs from the real path of a ${name} behind a symlink`, async (t) => {
+ const { dir, workspace, s, env } = await session(
+ t,
+ [
+ { name: "read", input: { path: `${escape}/secret.txt` } },
+ { name: "write", input: { path: `${escape}/planted.txt`, content: "planted\n" } },
+ { name: "read", input: { path: `${stay}/notes.txt` } },
+ { name: "write", input: { path: `${stay}/fine.md`, content: "inside\n" } },
+ ],
+ place,
+ );
+ mkdirSync(outside(dir), { recursive: true });
+ writeFileSync(join(outside(dir), "secret.txt"), "SECRET-OUTSIDE\n");
+ const r = await turn(env, "Message 1 from jason, class REQUEST:\n\nread and write", workspace);
+ assert.equal(r.code, 0, r.stderr);
+ assert.doesNotMatch(r.stdout, /SECRET/);
+ const results = JSON.parse(r.stdout.trim().slice("ANSWER ".length));
+ assert.equal(results.length, 4);
+ for (const i of [0, 1]) {
+ assert.equal(results[i][0], true);
+ assert.match(results[i][1], /outside the workspace/);
+ }
+ assert.deepEqual(readdirSync(outside(dir)), ["secret.txt"]);
+ assert.ok(!existsSync(join(workspace, "planted.txt")));
+ assert.deepEqual(results[2], [false, "inside\n"]);
+ assert.equal(results[3][0], false);
+ assert.ok(existsSync(join(workspace, "fine.md")), "the write inside landed");
+ assert.ok(existsSync(s.turnMarker));
+ });
+}
+
+test("pi: a missing extension refuses before any model call", async (t) => { +test("pi: a missing extension refuses before any model call", async (t) => {
+ const { dir, api, s, env } = await session(t, []); + const { dir, api, s, env } = await session(t, []);
+ const r = await turn({ ...env, MOSAIC_EXTENSIONS: join(dir, "missing.mjs") }, "x", s.workspace); + const r = await turn({ ...env, MOSAIC_EXTENSIONS: join(dir, "missing.mjs") }, "x", s.workspace);
@@ -15,11 +15,11 @@ e9eeec4bef3384b5b15d1e7a2c9d913d2f3e329228c3e3e2cacb8f741aa21379 packages/cli/s
0a2a452fdb3a4ea68478e54b3ca6694c51d074fa29a0ad8ea3740eb1b44ab780 packages/cli/tests/host.test.mjs 0a2a452fdb3a4ea68478e54b3ca6694c51d074fa29a0ad8ea3740eb1b44ab780 packages/cli/tests/host.test.mjs
1dbdb8166e8c47290bb4499b330ea32bec9a6f07179eef816edaa35603b1c408 packages/cli/tests/launcher.test.mjs 1dbdb8166e8c47290bb4499b330ea32bec9a6f07179eef816edaa35603b1c408 packages/cli/tests/launcher.test.mjs
709bd331bb0d76f28b464e518f4e1fc3bb0b303614e79d7e82479dcd679043cc packages/cli/tests/verbs.test.mjs 709bd331bb0d76f28b464e518f4e1fc3bb0b303614e79d7e82479dcd679043cc packages/cli/tests/verbs.test.mjs
570817222c5cc2f195963569e295db11f09f5dd56f19e3c9bdc220d5ee54435f packages/harness/README.md f996119e0afe972516408c8058c49d93192cabebbc48778cf9da5a9bbfa94a7c packages/harness/README.md
34ef8212e27f052223443c66830839517f7a54ecb6ef7d85795b3e89c65b4d4c packages/harness/package.json 34ef8212e27f052223443c66830839517f7a54ecb6ef7d85795b3e89c65b4d4c packages/harness/package.json
22efd0bed7991561920ad29f6bf9a1ab2d7384185fab4267684a037e026d8e85 packages/harness/src/bundle.mjs 22efd0bed7991561920ad29f6bf9a1ab2d7384185fab4267684a037e026d8e85 packages/harness/src/bundle.mjs
32b5090760c95eea0e1232ff36ec13a1d9b58335b2eade621196dec1ebdbe784 packages/harness/src/claude-gate.mjs 32b5090760c95eea0e1232ff36ec13a1d9b58335b2eade621196dec1ebdbe784 packages/harness/src/claude-gate.mjs
be416a0c70cd84c2c53e3259296077c28c5d3006e0a151efc950b2ce73354604 packages/harness/src/gate.mjs 38219ee9ea8bc9239e6de375a79e98a431338ec51eee0b71e865c4efea620765 packages/harness/src/gate.mjs
71e00113b8e5b55b410c7aae6232f76e972fc77aa68afa893da45c6b78d297e2 packages/harness/src/mcp-server.mjs 71e00113b8e5b55b410c7aae6232f76e972fc77aa68afa893da45c6b78d297e2 packages/harness/src/mcp-server.mjs
d19753fa72f0b57e751749359644093713bcb650bfac566f55d2bc05cb817121 packages/harness/src/pi-extension.mjs d19753fa72f0b57e751749359644093713bcb650bfac566f55d2bc05cb817121 packages/harness/src/pi-extension.mjs
1047aa092080e92efde2044dddaf82bacf428ed4b143c3846693471481f3612e packages/harness/src/runner.mjs 1047aa092080e92efde2044dddaf82bacf428ed4b143c3846693471481f3612e packages/harness/src/runner.mjs
@@ -28,10 +28,10 @@ d19753fa72f0b57e751749359644093713bcb650bfac566f55d2bc05cb817121 packages/harne
96524da43b212e84d78108cbbf05eef324c934f3ad9e0d4cb2edcb0f3df256d4 packages/harness/tests/claude-gate.test.mjs 96524da43b212e84d78108cbbf05eef324c934f3ad9e0d4cb2edcb0f3df256d4 packages/harness/tests/claude-gate.test.mjs
197ec0f6c842552bea65fb2ab4c8cb8615fd6e691a2d0a592e1465d18a45d75f packages/harness/tests/claude-session.test.mjs 197ec0f6c842552bea65fb2ab4c8cb8615fd6e691a2d0a592e1465d18a45d75f packages/harness/tests/claude-session.test.mjs
8392172b243356932c974bdca9b4c449a312ae7a042ee7a22e0f22fbb98dbec9 packages/harness/tests/fixtures/fake-adapter.mjs 8392172b243356932c974bdca9b4c449a312ae7a042ee7a22e0f22fbb98dbec9 packages/harness/tests/fixtures/fake-adapter.mjs
38111dc604d32486941f9422654cc392fa2c7767e7f02d13ebbb0038b71669ce packages/harness/tests/gate.test.mjs c406566d92f79dc74f52588549303309e6d843bc8013885c33fb8e5e12d89195 packages/harness/tests/gate.test.mjs
6e7509cfe6ad909440c25b750528360c0ba617c6c68b05d400bcd94b481c1f76 packages/harness/tests/helpers.mjs 6e7509cfe6ad909440c25b750528360c0ba617c6c68b05d400bcd94b481c1f76 packages/harness/tests/helpers.mjs
793eb11f970e4a8eecddec4dc48c24331e4de795bc04bfadf806a2fc3a15a8b4 packages/harness/tests/mcp-server.test.mjs 793eb11f970e4a8eecddec4dc48c24331e4de795bc04bfadf806a2fc3a15a8b4 packages/harness/tests/mcp-server.test.mjs
c4798bb5681172e707d41446388e0490ca8a5398e801766988c8daba2646beaf packages/harness/tests/pi-session.test.mjs 6d31a44a8e9835406df201e111d88fb3e6307c5993e471cce0d533153237e451 packages/harness/tests/pi-session.test.mjs
c8f23144316501c3e163cf5a5566ddee552b3c93dee13594ae8c1eac66aedc64 packages/harness/tests/runner.test.mjs c8f23144316501c3e163cf5a5566ddee552b3c93dee13594ae8c1eac66aedc64 packages/harness/tests/runner.test.mjs
ef9130a3cb1ca3e278a8ed370060afd1145d1ceb211a915e83d75c8346b04931 packages/harness/tests/tools.test.mjs ef9130a3cb1ca3e278a8ed370060afd1145d1ceb211a915e83d75c8346b04931 packages/harness/tests/tools.test.mjs
4e34456187387b02fa6d996c270dea4076b5d57952cddaa01f7a04843b351a02 packages/seat/README.md 4e34456187387b02fa6d996c270dea4076b5d57952cddaa01f7a04843b351a02 packages/seat/README.md
+75 -75
View File
@@ -1,77 +1,77 @@
✔ launch identity is stamped, payload identity is refused and stale holder cannot send (239.740458ms) ✔ launch identity is stamped, payload identity is refused and stale holder cannot send (167.142297ms)
✔ decision classes route from policy; gated resolution is human-only, choice and target must match (434.495891ms) ✔ decision classes route from policy; gated resolution is human-only, choice and target must match (230.420936ms)
✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (318.977003ms) ✔ claim exclusion, holder release, gated revoke and rerouting to a new holder are atomic (235.647129ms)
✔ launch events require a human CLI capability; generic emit cannot forge authority events (182.276491ms) ✔ launch events require a human CLI capability; generic emit cannot forge authority events (151.061215ms)
✔ within-role decisions close atomically and invalid options or blocking omissions refuse (193.552882ms) ✔ within-role decisions close atomically and invalid options or blocking omissions refuse (153.380732ms)
✔ observer capabilities read human inbox but cannot mutate or forge launch identity (170.799289ms) ✔ observer capabilities read human inbox but cannot mutate or forge launch identity (141.15041ms)
✔ task action subjects and linked decision trail are complete and ordered (203.941682ms) ✔ task action subjects and linked decision trail are complete and ordered (152.693428ms)
✔ launch binding is durable and reconnecting requires the identical trusted record (95.56779ms) ✔ launch binding is durable and reconnecting requires the identical trusted record (86.297407ms)
✔ business isolation includes inherited object names and cross-business message references (163.750481ms) ✔ business isolation includes inherited object names and cross-business message references (148.976508ms)
✔ authority never transfers between action, run, target, unresolved or replaced role holder (320.028764ms) ✔ authority never transfers between action, run, target, unresolved or replaced role holder (210.77989ms)
✔ task projection uses schema current view, skipping earlier and equal-start polls (133.747714ms) ✔ task projection uses schema current view, skipping earlier and equal-start polls (104.339254ms)
✔ revocation permanently bars the old run from reclaiming first, including after broker restart (191.711174ms) ✔ revocation permanently bars the old run from reclaiming first, including after broker restart (173.293573ms)
✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (134.762457ms) ✔ empty message references refuse before storage; refusal-evidence failure stays a typed error (102.099882ms)
✔ both arbiters require human resolution when their cross-role route is themselves (255.780592ms) ✔ both arbiters require human resolution when their cross-role route is themselves (159.939336ms)
✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.307073ms) ✔ S1 adapter takes resolved limits and refs, rejects mismatched instance, never mutates input (2.227341ms)
✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.946341ms) ✔ only validated broker references load; returned data and exceptions cannot expose a known token (4.907077ms)
✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (6.01105ms) ✔ bad file modes, symlinks, repository/data paths, malformed tokens and missing dates refuse (8.415907ms)
✔ expiry refuses use and env references never become client data (0.903117ms) ✔ expiry refuses use and env references never become client data (0.601154ms)
✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.796059ms) ✔ S1 parsed service refs work, service mismatch refuses, Gitea rotation due is a warning state (1.194888ms)
✔ opaque tokens shorter than 16 characters refuse before use (0.398635ms) ✔ opaque tokens shorter than 16 characters refuse before use (0.261952ms)
✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (206.587941ms) ✔ endLaunch writes session.ended, releases the run claim and kills its capabilities (130.971346ms)
✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (134.400371ms) ✔ endLaunch refuses an unknown run, a second end and a rebind of the ended run (111.098252ms)
✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (323.818329ms) ✔ a restarted broker refuses to rebind an ended run; a refused rebind leaves the run unbound (131.39879ms)
✔ endLaunch leaves a claim another run took alone (183.703048ms) ✔ endLaunch leaves a claim another run took alone (157.697482ms)
✔ refuse records action.refused against the caller with the code only (186.044736ms) ✔ refuse records action.refused against the caller with the code only (125.302528ms)
✔ launches off refuses role.launch with launch-revoked until launches on (189.472852ms) ✔ launches off refuses role.launch with launch-revoked until launches on (156.743888ms)
✔ broker process: launch ops authorize role.launch, record refusals and end runs (274.249507ms) ✔ broker process: launch ops authorize role.launch, record refusals and end runs (231.008594ms)
✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.232835ms) ✔ human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse (2.64164ms)
✔ process reader gets own kernel identity without exposing environment values (1.347663ms) ✔ process reader gets own kernel identity without exposing environment values (1.564657ms)
✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.961234ms) ✔ EACCES ancestor environments skip only markers; commands and registered launches still refuse (0.992806ms)
✔ real pid 1 remains inspectable when its environment is protected (0.369189ms) ✔ real pid 1 remains inspectable when its environment is protected (0.366038ms)
✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (253.116133ms) ✔ within-role sends cite an open gated launch decision without spending it or naming it in grants (192.270951ms)
✔ missing and foreign-business citations refuse and roll back message and grant (200.662761ms) ✔ missing and foreign-business citations refuse and roll back message and grant (165.248188ms)
✔ cross-role sends still need a matching resolved decision and consume it once (427.569214ms) ✔ cross-role sends still need a matching resolved decision and consume it once (225.177294ms)
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (253.423734ms) ✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (181.027469ms)
✔ startup token refusal returns safe code without value or partial listening broker (35.293578ms) ✔ startup token refusal returns safe code without value or partial listening broker (37.780904ms)
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (291.67146ms) ✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (157.839221ms)
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (192.094911ms) ✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (164.036638ms)
✔ trusted host registers later launches; socket clients never have a registration verb (239.798505ms) ✔ trusted host registers later launches; socket clients never have a registration verb (163.83318ms)
✔ runtime excludes declared project roots even when host supplies no repoRoots (37.521057ms) ✔ runtime excludes declared project roots even when host supplies no repoRoots (37.888926ms)
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (167.72013ms) ✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (145.998882ms)
✔ v3b prototype refusals, views and append-only mutations (1374.206323ms) ✔ v3b prototype refusals, views and append-only mutations (931.868277ms)
✔ gated approval authorizes once, survives store reopen, and fresh approval works (315.775301ms) ✔ gated approval authorizes once, survives store reopen, and fresh approval works (234.144952ms)
✔ another run cannot consume an approval; a failed check leaves it usable (391.815636ms) ✔ another run cannot consume an approval; a failed check leaves it usable (192.203781ms)
✔ two scheduled callers have exactly one grant and one consumed refusal (203.799579ms) ✔ two scheduled callers have exactly one grant and one consumed refusal (152.903468ms)
✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (362.938983ms) ✔ failed commit rolls consumption back; cross-role consumes and within-role stays reusable (273.028459ms)
✔ class drift gated to cross-role refuses before consumption (228.029982ms) ✔ class drift gated to cross-role refuses before consumption (176.737816ms)
✔ class drift cross-role to gated refuses before consumption (244.576932ms) ✔ class drift cross-role to gated refuses before consumption (173.106468ms)
✔ class drift gated to within-role refuses before consumption (177.896571ms) ✔ class drift gated to within-role refuses before consumption (153.556383ms)
✔ class drift cross-role to within-role refuses before consumption (195.452584ms) ✔ class drift cross-role to within-role refuses before consumption (172.837053ms)
✔ class drift within-role to gated refuses before consumption (243.556487ms) ✔ class drift within-role to gated refuses before consumption (150.262194ms)
✔ class drift within-role to cross-role refuses before consumption (179.418758ms) ✔ class drift within-role to cross-role refuses before consumption (146.76894ms)
✔ message.send consumes approval and prevents a later send or authorize (199.875778ms) ✔ message.send consumes approval and prevents a later send or authorize (166.63171ms)
✔ role.revoke consumes approval and prevents a later revoke or authorize (269.090073ms) ✔ role.revoke consumes approval and prevents a later revoke or authorize (189.942568ms)
✔ creates private WAL store and excludes a second writer until explicit close (184.52587ms) ✔ creates private WAL store and excludes a second writer until explicit close (130.323883ms)
✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (177.881186ms) ✔ rollback is atomic and schema metadata is checked against trusted DDL, not just itself (151.473064ms)
✔ existing empty database and symlink runtime directory refuse, never initialize over damage (357.948758ms) ✔ existing empty database and symlink runtime directory refuse, never initialize over damage (160.113086ms)
✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (218.819903ms) ✔ crash during a transaction recovers no partial event after explicit fixture-only lock removal (159.365395ms)
✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (101.258819ms) ✔ writer refuses mixed at/read_at forms atomically, even through trusted SQL helpers (99.519215ms)
✔ async transactions refuse before invoking their function (96.216937ms) ✔ async transactions refuse before invoking their function (75.624887ms)
✔ recordTask keeps sync reads and a role write apart (262.686766ms) ✔ recordTask keeps sync reads and a role write apart (176.035958ms)
✔ read_at must be one canonical UTC format, so the projection compares strings safely (116.864183ms) ✔ read_at must be one canonical UTC format, so the projection compares strings safely (102.593544ms)
✔ a bad entry refuses the whole record (273.912315ms) ✔ a bad entry refuses the whole record (98.782671ms)
✔ taskView reads the projection for one business (139.393069ms) ✔ taskView reads the projection for one business (98.322625ms)
✔ requestTask hands only a holder and a task verb to the handler, and records refusals (298.358617ms) ✔ requestTask hands only a holder and a task verb to the handler, and records refusals (226.263728ms)
✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (434.226779ms) ✔ the server sends task verbs to the adapter with its own timeout; other verbs stay synchronous (390.207533ms)
✔ without an adapter the server refuses every task verb (214.917219ms) ✔ without an adapter the server refuses every task verb (165.545934ms)
✔ the runtime refuses an invalid adapter and closes a valid one (193.702117ms) ✔ the runtime refuses an invalid adapter and closes a valid one (170.701529ms)
✔ the process loads the S3 adapter from plain-data trackers (305.830649ms) ✔ the process loads the S3 adapter from plain-data trackers (226.542656ms)
✔ socket capability stamps launch identity; shared views use wire, no SQL client (218.013406ms) ✔ socket capability stamps launch identity; shared views use wire, no SQL client (145.169982ms)
✔ two wire claims serialize; a lost reply never automatically retries (188.544206ms) ✔ two wire claims serialize; a lost reply never automatically retries (164.806381ms)
✔ malformed, oversized and identity-forging envelopes refuse without echoing input (268.564851ms) ✔ malformed, oversized and identity-forging envelopes refuse without echoing input (104.686616ms)
✔ client preserves UTF-8 when a response divides a multibyte character (11.877466ms) ✔ client preserves UTF-8 when a response divides a multibyte character (11.749373ms)
✔ committed mutation followed by dropped reply reports unknown and is never retried (149.111642ms) ✔ committed mutation followed by dropped reply reports unknown and is never retried (127.787676ms)
ℹ tests 74 ℹ tests 74
ℹ suites 0 ℹ suites 0
ℹ pass 74 ℹ pass 74
@@ -79,4 +79,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 3108.809343 ℹ duration_ms 2287.87016
+61 -61
View File
@@ -1,63 +1,63 @@
✔ config directory and file path follow MOSAIC_CONFIG (1.94594ms) ✔ config directory and file path follow MOSAIC_CONFIG (1.885852ms)
✔ the fixture business validates and comes back frozen (5.707571ms) ✔ the fixture business validates and comes back frozen (5.658424ms)
✔ two instances may share a definition (1.972691ms) ✔ two instances may share a definition (1.339683ms)
✔ top-level refusals (5.720077ms) ✔ top-level refusals (3.506042ms)
✔ arbiters and projects (7.599597ms) ✔ arbiters and projects (5.159999ms)
✔ role instances (3.911216ms) ✔ role instances (3.489544ms)
✔ Vikunja bots (8.160669ms) ✔ Vikunja bots (6.636274ms)
✔ a role without Vikunja takes no tracker block (3.078813ms) ✔ a role without Vikunja takes no tracker block (3.122115ms)
✔ credential references match the definition's services (3.806947ms) ✔ credential references match the definition's services (3.679316ms)
✔ launch (10.102968ms) ✔ launch (11.088925ms)
✔ loadBusiness: file checks (2.022471ms) ✔ loadBusiness: file checks (2.76976ms)
✔ loadBusiness: not a regular file (45.202904ms) ✔ loadBusiness: not a regular file (39.208263ms)
✔ loading writes nothing (1.253617ms) ✔ loading writes nothing (1.965275ms)
✔ names that are Object.prototype properties don't count as declared (2.657347ms) ✔ names that are Object.prototype properties don't count as declared (2.547254ms)
✔ the shipped example refuses as written and validates once filled in (0.542513ms) ✔ the shipped example refuses as written and validates once filled in (0.504688ms)
✔ usage errors exit 4 (285.619026ms) ✔ usage errors exit 4 (291.725474ms)
✔ validate: a good business exits 0 and prints instance digests (61.986118ms) ✔ validate: a good business exits 0 and prints instance digests (71.185766ms)
✔ validate: project files (315.802361ms) ✔ validate: project files (320.543732ms)
✔ validate: missing files and a broken system config (235.381833ms) ✔ validate: missing files and a broken system config (230.119986ms)
✔ validate: credential reference problems exit 2 and name each one (62.229075ms) ✔ validate: credential reference problems exit 2 and name each one (64.013596ms)
✔ validate: a token file inside the repository is refused (64.115882ms) ✔ validate: a token file inside the repository is refused (64.848806ms)
✔ validate: role definitions come from MOSAIC_ROLES_DIR (200.324478ms) ✔ validate: role definitions come from MOSAIC_ROLES_DIR (185.260127ms)
✔ resolve: prints one instance's record (193.814279ms) ✔ resolve: prints one instance's record (194.639072ms)
✔ resolve: refusals (386.379682ms) ✔ resolve: refusals (379.459948ms)
✔ parse: exactly one of file or env, plus the service's date (3.254409ms) ✔ parse: exactly one of file or env, plus the service's date (3.370475ms)
✔ check: a good file has no problems (1.798437ms) ✔ check: a good file has no problems (0.962955ms)
✔ check never opens the file: a write-only token passes (0.467731ms) ✔ check never opens the file: a write-only token passes (0.425714ms)
✔ check: file problems (1.279571ms) ✔ check: file problems (1.175521ms)
✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.400708ms) ✔ check: token files can't live in the repository or dataRoot, even through a linked directory (1.182776ms)
✔ check: dates and environment references (0.575693ms) ✔ check: dates and environment references (0.537616ms)
✔ path and load (2.919347ms) ✔ path and load (3.154659ms)
✔ refusals (1.710575ms) ✔ refusals (1.625627ms)
✔ systemVars flattens the validated config (2.516987ms) ✔ systemVars flattens the validated config (2.569268ms)
✔ precedence: system, business, project, project role, agent (6.390188ms) ✔ precedence: system, business, project, project role, agent (6.532793ms)
✔ limits narrow the definition and never widen it (3.070619ms) ✔ limits narrow the definition and never widen it (3.03896ms)
✔ role.launch stays within-role only for the instance the launch block names (6.400121ms) ✔ role.launch stays within-role only for the instance the launch block names (6.526328ms)
✔ limits.authority without role.launch leaves the launcher with no launch block (1.722667ms) ✔ limits.authority without role.launch leaves the launcher with no launch block (2.854804ms)
✔ limits.authority narrows cross-role actions too (1.035999ms) ✔ limits.authority narrows cross-role actions too (1.628806ms)
✔ classify (1.279788ms) ✔ classify (1.759815ms)
✔ the record carries what the broker and launcher need (1.144397ms) ✔ the record carries what the broker and launcher need (1.618335ms)
✔ digest: key order doesn't matter, any value change does (8.9708ms) ✔ digest: key order doesn't matter, any value change does (9.545966ms)
✔ refusals (3.013121ms) ✔ refusals (2.287967ms)
✔ the four shipped version 2 roles load (5.279908ms) ✔ the four shipped version 2 roles load (3.980318ms)
✔ shipped role scopes match addendum B section 2 and the SR runbook (1.490848ms) ✔ shipped role scopes match addendum B section 2 and the SR runbook (1.340415ms)
✔ shipped authority follows the note's table (0.764831ms) ✔ shipped authority follows the note's table (0.727965ms)
✔ version 1 files keep loading with no authority (1.274805ms) ✔ version 1 files keep loading with no authority (1.265329ms)
✔ the conductor policy isn't a role (0.306126ms) ✔ the conductor policy isn't a role (0.308318ms)
✔ a missing role file is exit 4, a symbolic link too (0.509379ms) ✔ a missing role file is exit 4, a symbolic link too (0.53197ms)
✔ version 2 refusals (1.439143ms) ✔ version 2 refusals (1.734995ms)
✔ authority: closed vocabulary, no gated-only action, no overlap (2.33389ms) ✔ authority: closed vocabulary, no gated-only action, no overlap (3.184459ms)
✔ credentials: Gitea scopes (0.923002ms) ✔ credentials: Gitea scopes (1.293834ms)
✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.222158ms) ✔ credentials: Vikunja scopes are a group-to-verbs map from the grantable list (1.699624ms)
✔ credentials: services (0.632125ms) ✔ credentials: services (0.850815ms)
✔ contract: a non-empty regular Markdown file beside the role file (1.111439ms) ✔ contract: a non-empty regular Markdown file beside the role file (1.165891ms)
✔ every key names known layers and a merge rule (1.289197ms) ✔ every key names known layers and a merge rule (1.208191ms)
✔ unknown keys and wrong layers refuse (0.896979ms) ✔ unknown keys and wrong layers refuse (0.984427ms)
✔ types (1.783348ms) ✔ types (2.450103ms)
✔ merge: defaults, then the most specific layer wins (0.30552ms) ✔ merge: defaults, then the most specific layer wins (0.37003ms)
✔ merge: limits only narrow, and provenance lists each source (0.393139ms) ✔ merge: limits only narrow, and provenance lists each source (0.490281ms)
✔ merge doesn't change its inputs (0.177042ms) ✔ merge doesn't change its inputs (0.19074ms)
ℹ tests 60 ℹ tests 60
ℹ suites 0 ℹ suites 0
ℹ pass 60 ℹ pass 60
@@ -65,4 +65,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 1887.219031 ℹ duration_ms 1878.19385
+84 -84
View File
@@ -1,88 +1,88 @@
✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (116.16035ms) ✔ inbox lists only decisions routed to the human, with what approving authorizes and how to decide (109.821098ms)
✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (132.767677ms) ✔ decide resolves by id prefix with --yes, and the trail shows it in broker order with the task to follow (114.050715ms)
✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (112.711559ms) ✔ decide refuses without a terminal or --yes, on an unknown option and on a short reference (93.336022ms)
✔ decide prints a declining choice as declining (109.36668ms) ✔ decide prints a declining choice as declining (110.188339ms)
✔ an unknown outcome is reported once and never resent (99.216529ms) ✔ an unknown outcome is reported once and never resent (87.255753ms)
✔ a decision closed before the answer arrives exits 2 and points at its trail (99.275753ms) ✔ a decision closed before the answer arrives exits 2 and points at its trail (90.124364ms)
✔ a prefix that matches two open decisions exits 2 and resolves neither (79.160142ms) ✔ a prefix that matches two open decisions exits 2 and resolves neither (77.82934ms)
✔ without --business a command uses the live host's business, and a stale host.json is not a host (81.978588ms) ✔ without --business a command uses the live host's business, and a stale host.json is not a host (48.600651ms)
✔ every human command refuses inside an agent run before it touches the bus (106.067287ms) ✔ every human command refuses inside an agent run before it touches the bus (71.832475ms)
✔ usage errors exit 4; no business and no host is a usage error (102.352812ms) ✔ usage errors exit 4; no business and no host is a usage error (76.458185ms)
✔ agents and tasks print through the broker (103.812899ms) ✔ agents and tasks print through the broker (94.030107ms)
✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.186116ms) ✔ notify.json: missing, loose, malformed and extra keys refuse; a binding or null passes (1.432523ms)
✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (42.538119ms) ✔ bootConfig builds the broker's boot message for one business, with no trackers key when no project names one (38.895541ms)
✔ trackers come from the tracker.* variables of the one project that names a tracker project (38.865355ms) ✔ trackers come from the tracker.* variables of the one project that names a tracker project (34.10218ms)
✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (40.992134ms) ✔ with two projects, the one that sets tracker.project supplies the tracker and the other is no conflict (33.11702ms)
✔ two projects that each name a tracker project refuse, since the boot shape holds one (37.564192ms) ✔ two projects that each name a tracker project refuse, since the boot shape holds one (31.703833ms)
✔ a business without tracker.baseUrl gets no trackers entry (31.426747ms) ✔ a business without tracker.baseUrl gets no trackers entry (29.926688ms)
✔ an unknown business and a broken system config refuse with exit 3 (54.512934ms) ✔ an unknown business and a broken system config refuse with exit 3 (52.638178ms)
✔ empty views say so (0.965635ms) ✔ empty views say so (0.663714ms)
✔ the trail keeps the broker's order and names a decision's task without its rows (1.169355ms) ✔ the trail keeps the broker's order and names a decision's task without its rows (0.76844ms)
✔ tasks print the tracker fields the snapshot carries (0.231141ms) ✔ tasks print the tracker fields the snapshot carries (0.141757ms)
✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (921.823079ms) ✔ the host boots the broker, binds a launch in process, and the notifier DMs a blocking decision exactly once (875.484388ms)
✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (234.249151ms) ✔ a notifier that dies takes the host down with exit 1, so the unit restarts the pair (222.741237ms)
✔ a second host for the same data root refuses with exit 3 while the first runs (145.474385ms) ✔ a second host for the same data root refuses with exit 3 while the first runs (113.902862ms)
✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1163.334044ms) ✔ a broker reply that misses the wait breaks the channel: the late reply answers nothing, later requests refuse, the host exits 1 (1113.784714ms)
✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (234.382551ms) ✔ a broker reply with another request's id, or none, breaks the channel and the host exits 1 (272.214333ms)
✔ a broker reply with no request waiting breaks the channel and the host exits 1 (143.162698ms) ✔ a broker reply with no request waiting breaks the channel and the host exits 1 (132.624701ms)
✔ a notifier that refuses stops the broker and the host refuses with exit 3 (161.984306ms) ✔ a notifier that refuses stops the broker and the host refuses with exit 3 (209.872831ms)
✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (156.804707ms) ✔ a notifier that refuses after the broker died still refuses with exit 3, without a send to the dead broker (155.868613ms)
✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (101.744364ms) ✔ a notifier that dies before it replies, after the broker died, still refuses, without a send to the dead broker (101.015421ms)
✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (148.28775ms) ✔ a close send that fails with EPIPE after the notifier refuses still gives the notifier's refusal, exit 3 (147.852004ms)
✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (103.666899ms) ✔ a close send that fails with EPIPE after the notifier dies unanswered still gives the notifier's error (107.019954ms)
✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (163.923058ms) ✔ close() whose stop and close sends fail with EPIPE still finishes, with exit 1 (157.980688ms)
✔ watchChildren reports a child that died before it was called, and one that dies later (22.293656ms) ✔ watchChildren reports a child that died before it was called, and one that dies later (22.95826ms)
✔ bus stop refuses to signal a live pid that is not a bus host (202.92148ms) ✔ bus stop refuses to signal a live pid that is not a bus host (202.708424ms)
✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (204.29267ms) ✔ bus start refuses with exit 3 and the code when the broker refuses to boot; bus status names the lock (217.25076ms)
✔ bus start refuses with exit 3 without a notifier config (88.991059ms) ✔ bus start refuses with exit 3 without a notifier config (90.309442ms)
✔ bus start runs until bus stop; status reports it while it runs (656.946975ms) ✔ bus start runs until bus stop; status reports it while it runs (671.246535ms)
✔ bus-service.sh renders the unit and installs it into a given directory (26.216533ms) ✔ bus-service.sh renders the unit and installs it into a given directory (26.06789ms)
✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (285.286099ms) ✔ bus start --pm: the PM runs under its own PID namespace, registered, with a manifest (1259.700815ms)
✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (677.971127ms) ✔ the PM launches a coder through its launch tool; the coder answers; refusals name their code (579.017824ms)
✔ a runner that stops at once ends its launch with the runner's reason (266.797745ms) ✔ a runner that stops at once ends its launch with the runner's reason (190.735678ms)
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (698.435904ms) ✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (678.538547ms)
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3619.790341ms) ✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (3616.19803ms)
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (127.464512ms) ✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (107.76491ms)
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (138.102925ms) ✔ an over-long launch request is refused at once, not at the 10 s idle timeout (117.120068ms)
✔ a launch client that never closes its side doesn't hold the host's close (123.236149ms) ✔ a launch client that never closes its side doesn't hold the host's close (119.733944ms)
✔ a runner that ignores SIGTERM is killed when the host closes (1222.473883ms) ✔ a runner that ignores SIGTERM is killed when the host closes (1231.559454ms)
✔ zoned uses the IANA zone across DST (15.721138ms) ✔ zoned uses the IANA zone across DST (19.652324ms)
✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (121.040523ms) ✔ each open blocking decision is DM'd once, across polls and a restart; non-blocking ones are not (104.108826ms)
✔ two blocking decisions get two DMs with different nonces (132.938253ms) ✔ two blocking decisions get two DMs with different nonces (112.469993ms)
✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.184982ms) ✔ the digest nonce differs per business and per day and fits Discord's 25 characters (0.172163ms)
✔ a failed DM is journaled, backs off, and is retried until it lands (103.264969ms) ✔ a failed DM is journaled, backs off, and is retried until it lands (105.067217ms)
✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (111.979075ms) ✔ five definite refusals stop a DM: one gave-up line, one log line, and a restart keeps the count (93.834153ms)
✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (96.261206ms) ✔ 429s, 5xx-style unknowns and refusals without a status never count toward the limit (83.061084ms)
✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (109.787479ms) ✔ a crash between the fifth refusal and its gave-up line: the next poll appends it and sends nothing (82.164605ms)
✔ 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 (141.731781ms) ✔ 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.69866ms)
✔ a restart after the second refusal does not send before that refusal's 30 min are up (131.77121ms) ✔ a restart after the second refusal does not send before that refusal's 30 min are up (111.61552ms)
✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (135.646325ms) ✔ the digest goes at 08:00 Chicago once a day, with blocking ones marked as DM'd (114.794865ms)
✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (121.708992ms) ✔ a late start with no digest for the day sends one at once; an empty inbox gets one line (77.93107ms)
✔ an inbox read failure is logged and the next poll retries (0.643351ms) ✔ an inbox read failure is logged and the next poll retries (0.608316ms)
✔ no Discord id reaches the journal or the log (97.433045ms) ✔ no Discord id reaches the journal or the log (49.790922ms)
✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (13.455834ms) ✔ the journal: a torn tail is copied out and truncated, so an append after it reopens cleanly (16.051158ms)
✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (26.085454ms) ✔ the journal: a crash between the copy and the truncate leaves a tail the next open repairs (24.73249ms)
✔ the journal: a whole file that is one torn line truncates to empty (22.000323ms) ✔ the journal: a whole file that is one torn line truncates to empty (10.327754ms)
✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (2.339076ms) ✔ the journal: a malformed complete line refuses and leaves the file and any torn tail alone (0.625063ms)
✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.593157ms) ✔ the journal: a loose file mode, a loose directory or a symlinked journal refuses (0.601874ms)
✔ the journal: a line with a wrong type refuses with exit 3 and names the field (4.170535ms) ✔ the journal: a line with a wrong type refuses with exit 3 and names the field (1.880814ms)
✔ the journal: a symlinked directory refuses and says it is a link (0.308408ms) ✔ the journal: a symlinked directory refuses and says it is a link (0.328759ms)
✔ 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.463907ms) ✔ 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.473835ms)
✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.378159ms) ✔ the journal: an append after the file was swapped for a symlink refuses and writes nothing through it (0.390065ms)
✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.576535ms) ✔ the journal: a directory it cannot write or create refuses with exit 3 and names the path (0.411686ms)
✔ digest content stays within Discord's 2000 characters (0.254664ms) ✔ digest content stays within Discord's 2000 characters (0.265279ms)
✔ runLoop never overlaps ticks and stops after the one in flight (110.674806ms) ✔ runLoop never overlaps ticks and stops after the one in flight (111.390327ms)
task.close {} answered: invalid-request; fake saw 18 requests, first GET /info 200, GET /projects/1 200, GET /projects/1/views 200 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 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 (388.552078ms) ✔ bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja (360.024844ms)
✔ the transport writes {business, verb, args} to the child and reads its JSON (33.653582ms) ✔ the transport writes {business, verb, args} to the child and reads its JSON (32.113859ms)
✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2175.341854ms) ✔ a bus code on stderr becomes the exit code; garbage and timeouts are outcome problems (2159.761004ms)
✔ busExit and refuseInsideAgent (0.649603ms) ✔ busExit and refuseInsideAgent (0.402234ms)
✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (222.190237ms) ✔ talk sends a REQUEST, prints and reads everything that arrives, and stops at the reply (190.507141ms)
✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1115.846632ms) ✔ talk --wait 0 only sends; no reply within --wait exits 1 and says where it will show (1127.402634ms)
✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (95.891317ms) ✔ talk, stop and launches off/on refuse inside an agent run; bad arguments are usage errors (69.059366ms)
✔ launches off and on go to the broker and change the business's launch state (103.309056ms) ✔ launches off and on go to the broker and change the business's launch state (71.916292ms)
✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (64.963523ms) ✔ launches list reads sessions.json and marks a stale entry; stop reports it and refuses a non-runner (69.964586ms)
ℹ tests 83 ℹ tests 83
ℹ suites 0 ℹ suites 0
ℹ pass 83 ℹ pass 83
@@ -90,4 +90,4 @@ task.close on a missing task answered: task-not-found; it made GET /tasks/999 40
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 7229.556012 ℹ duration_ms 7977.647548
+125 -125
View File
@@ -1,127 +1,127 @@
✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (4.096287ms) ✔ explicit request, Seen, ordinary completion and a new request have distinct attention states (3.516208ms)
✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.242785ms) ✔ attention convention ignores reasoning/quoted examples and permits leading blank lines (0.221007ms)
✔ completed smoke replies and ordinary questions are idle, not human blockers (0.846915ms) ✔ completed smoke replies and ordinary questions are idle, not human blockers (1.021704ms)
✔ only an explicit first-line input request makes a finished reply waiting (0.171746ms) ✔ only an explicit first-line input request makes a finished reply waiting (0.214674ms)
✔ tool activity, user text, errors and unfinished turns override attention text (0.136452ms) ✔ tool activity, user text, errors and unfinished turns override attention text (0.201649ms)
✔ STOP access failure is unknown, not absence, under a non-root identity (38.327363ms) ✔ STOP access failure is unknown, not absence, under a non-root identity (34.432562ms)
✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (3.000707ms) ✔ connector Task never inherits Discord routing envelopes; ordinary Task still uses user text (2.75456ms)
✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.497489ms) ✔ connector discovery keeps only safe identity; rejects modes, mismatches, links and traversal (1.225648ms)
✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (5.879171ms) ✔ canonical owner identity and STOP are independent; no tmux fallback or forged registration (4.882625ms)
✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.584339ms) ✔ connector reply refusal precedes forged live tmux registration; ordinary agent still sends (0.494122ms)
✔ server rescans connector discovery and refuses HTTP reply without transport (35.559009ms) ✔ server rescans connector discovery and refuses HTTP reply without transport (32.30068ms)
✔ connector session links and linked directories are not read (1.077521ms) ✔ connector session links and linked directories are not read (1.085108ms)
✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (3.749232ms) ✔ newer live matching launch marks old activity, preserves history/attention/attribution, then clears on new activity (2.307688ms)
✔ CLI print uses the relaunch notice instead of old current preview (61.194863ms) ✔ CLI print uses the relaunch notice instead of old current preview (59.647081ms)
✔ connector owner and fixed task never inherit a native relaunch notice (2.122352ms) ✔ connector owner and fixed task never inherit a native relaunch notice (1.913401ms)
✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.149868ms) ✔ equality, stale/unknown/offline, mismatched registration and unknown activity do not assert relaunch (1.034946ms)
✔ loadConfig: missing file throws ConfigError (2.276188ms) ✔ loadConfig: missing file throws ConfigError (1.421104ms)
✔ loadConfig: invalid JSON throws ConfigError (0.410099ms) ✔ loadConfig: invalid JSON throws ConfigError (0.285492ms)
✔ loadConfig: missing dataRoot throws ConfigError (0.325667ms) ✔ loadConfig: missing dataRoot throws ConfigError (0.21408ms)
✔ loadConfig: relative dataRoot throws ConfigError (0.324818ms) ✔ loadConfig: relative dataRoot throws ConfigError (0.2884ms)
✔ loadConfig: valid config returns dataRoot (0.983333ms) ✔ loadConfig: valid config returns dataRoot (0.759311ms)
✔ findNewestSession: picks the newest by mtime among two files (0.644738ms) ✔ findNewestSession: picks the newest by mtime among two files (0.45778ms)
✔ findNewestSession: finds files in nested subdirectories (0.3859ms) ✔ findNewestSession: finds files in nested subdirectories (0.275495ms)
✔ findNewestSession: returns null for a missing dir (0.126045ms) ✔ findNewestSession: returns null for a missing dir (0.114267ms)
✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.665906ms) ✔ readSession: extracts fields, collapses/truncates text, counts a truncated final line (0.621165ms)
✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.644308ms) ✔ readSession: model and provider follow the latest model_change entry or assistant turn; null when the log names neither; scanAgent carries them (1.160846ms)
✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.50558ms) ✔ readSession: lastError carries the assistant errorMessage only when the last assistant turn errored (0.445693ms)
✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.156336ms) ✔ findNewestSession/scan: never read sibling auth or secrets next to a sessions dir (1.029261ms)
✔ deriveState: full state table (0.18949ms) ✔ deriveState: full state table (0.169314ms)
✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.317703ms) ✔ rule: newest entry is an assistant message with a tool call, after a question-looking text, is working (0.299936ms)
✔ rule: newest entry is a tool result with no assistant text after it is working (0.275116ms) ✔ rule: newest entry is a tool result with no assistant text after it is working (0.258709ms)
✔ rule: a finished ordinary turn is idle, even if it says your move (0.335356ms) ✔ rule: a finished ordinary turn is idle, even if it says your move (0.248311ms)
✔ task: the first user message of the session, from text blocks (0.252296ms) ✔ task: the first user message of the session, from text blocks (0.239443ms)
✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.247832ms) ✔ task: a plain-string user content is accepted, whitespace collapsed and long text capped (0.285634ms)
✔ task: no user message in the log means null (shown as unknown), never a guess (0.302295ms) ✔ task: no user message in the log means null (shown as unknown), never a guess (0.262166ms)
✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.384643ms) ✔ workspace: the live tmux pane path wins; the session cwd is the fallback; neither means null (0.355924ms)
✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.569899ms) ✔ activeProject: basename of the nearest .git directory or .git file above the workspace; none means null (0.479249ms)
✔ scan: the written record carries task, workspace and activeProject (0.505496ms) ✔ scan: the written record carries task, workspace and activeProject (0.440493ms)
✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.613266ms) ✔ registration: overrides task, project and workspace; every source says registration; registered carries the launch fields; the grouping column is untouched (0.556582ms)
✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.40108ms) ✔ registration: empty task and null project/workspace leave the derived values in place; registered is still non-null (0.372034ms)
✔ registration: a record whose pid is gone is stale; derived values win, sources say derived, registered stays with alive false; a pid the probe cannot decide is not stale; pidAlive itself (1.061634ms) ✔ 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.969936ms)
✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.447677ms) ✔ registration: no registration leaves the Gate A fields exactly as before, and registered is null (0.434541ms)
✔ loadRegistrations: a missing seatsDir gives empty lists (0.156698ms) ✔ loadRegistrations: a missing seatsDir gives empty lists (0.14972ms)
✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.751044ms) ✔ loadRegistrations: one good record, one malformed JSON, one with an unknown field; a stray file under seatsDir is ignored (0.809575ms)
✔ 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.33041ms) ✔ 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.322588ms)
✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.730321ms) ✔ scan: writes the registration override to disk; index.json carries registered and registrationErrors (0.689167ms)
✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.688744ms) ✔ scan: a relative seatsDir throws ConfigError; an omitted seatsDir behaves as before (0.293359ms)
✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.569466ms) ✔ scanAgent: waitingOnYou is true for waiting/error and false otherwise (0.549315ms)
✔ scanAgent: ageSeconds is computed from the injected now (0.263488ms) ✔ scanAgent: ageSeconds is computed from the injected now (0.274554ms)
✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.185838ms) ✔ scanAgent: sessionFile null and state idle when sessions dir is empty but alive (0.212229ms)
✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.323154ms) ✔ discoverRepoAgents: finds agents with a sessions dir, skips those without, sorted by name (0.317955ms)
✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.329147ms) ✔ discoverFleetAgents: finds agents with a sessions dir, sorted by name, fleet tmux fields (0.333089ms)
✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.846915ms) ✔ scan: writes per-agent files and index.json, rerun overwrites, no leftover tmp files (0.874775ms)
✔ scan: relative boardDir throws ConfigError (0.116027ms) ✔ scan: relative boardDir throws ConfigError (0.101351ms)
✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (60.587692ms) ✔ CLI: scan with assume-alive liveness exits 0, prints board summary, writes board files (56.416439ms)
✔ CLI: missing config exits 2 with a refused: message (60.171409ms) ✔ CLI: missing config exits 2 with a refused: message (50.322403ms)
✔ CLI: unknown command exits 2 (52.257478ms) ✔ CLI: unknown command exits 2 (49.401288ms)
✔ CLI: unknown --liveness value exits 2 (49.224582ms) ✔ CLI: unknown --liveness value exits 2 (51.189689ms)
✔ panesRunPi: true when any trimmed line equals 'pi' (0.178207ms) ✔ panesRunPi: true when any trimmed line equals 'pi' (0.193137ms)
✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.064143ms) ✔ panesRunPi: false for bash-only, claude, empty, or node-pi-style lines (0.072732ms)
✔ tmuxIsAlive: a pane running pi is alive (0.164539ms) ✔ tmuxIsAlive: a pane running pi is alive (0.182205ms)
✔ tmuxIsAlive: session exists but pi has exited is not alive (0.078587ms) ✔ tmuxIsAlive: session exists but pi has exited is not alive (0.071052ms)
✔ tmuxIsAlive: no such tmux session is not alive (0.046507ms) ✔ tmuxIsAlive: no such tmux session is not alive (0.048439ms)
✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.057703ms) ✔ tmuxIsAlive: tmux could not be run at all is unknown (null), never assumed alive (0.067025ms)
✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.084943ms) ✔ tmuxIsAlive: passes -L <socket> only when a socket is given (0.091318ms)
✔ parsePanes: one pane per line, command and optional tab-separated path (0.083186ms) ✔ parsePanes: one pane per line, command and optional tab-separated path (0.074592ms)
✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.068346ms) ✔ tmuxInspect: reports the path of the pane running pi, not of a shell pane (0.066011ms)
✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.085678ms) ✔ tmuxInspect: no pi pane, no session, or no tmux gives no workspace and the matching liveness (0.091921ms)
✔ loadSeen: missing file returns {} (0.143595ms) ✔ loadSeen: missing file returns {} (0.141722ms)
✔ loadSeen: invalid JSON throws ConfigError (0.176736ms) ✔ loadSeen: invalid JSON throws ConfigError (0.177117ms)
✔ loadSeen: a JSON array throws ConfigError (0.146391ms) ✔ loadSeen: a JSON array throws ConfigError (0.149854ms)
✔ loadSeen: a non-string value throws ConfigError (0.1506ms) ✔ loadSeen: a non-string value throws ConfigError (0.161851ms)
✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.317689ms) ✔ markSeen: seen true adds the key and writes seen.json mode 0600, no leftover tmp files (0.323351ms)
✔ markSeen: seen false deletes the key (0.245251ms) ✔ markSeen: seen false deletes the key (0.271685ms)
✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.190901ms) ✔ markSeen: missing, empty, or non-string fields throw ConfigError (0.226171ms)
✔ markSeen: project containing '/' throws ConfigError (0.114614ms) ✔ markSeen: project containing '/' throws ConfigError (0.125133ms)
✔ markSeen: non-boolean seen throws ConfigError (0.097122ms) ✔ markSeen: non-boolean seen throws ConfigError (0.113424ms)
✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.270157ms) ✔ scanAgent: a seen mark matching the waiting session's lastTimestamp clears waitingOnYou (0.298807ms)
✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.207344ms) ✔ scanAgent: a stale mark (agent wrote something newer) is not seen and waitingOnYou is true (0.219178ms)
✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.221339ms) ✔ scanAgent: a working session with a matching mark is not seen (marks only apply to waiting/error) (0.216414ms)
✔ scanAgent: an error-state session with a matching mark is seen (0.22026ms) ✔ scanAgent: an error-state session with a matching mark is seen (0.23189ms)
✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.461494ms) ✔ scan: index.seen and waitingOnYou reflect seen.json, which scan never rewrites or deletes (0.491272ms)
✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.168406ms) ✔ scan: a corrupt seen.json makes scan throw ConfigError (fail closed) (0.188284ms)
✔ 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.584854ms) ✔ 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.597317ms)
✔ 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.559492ms) ✔ 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.567468ms)
✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (0.887582ms) ✔ taskSetBy: scan() reads the field from disk through the seat package (bounded there), writes it to the per-agent record and index, and an invalid on-disk value is a registrationError, never a row value (0.907092ms)
✔ isLoopbackHost: recognizes loopback hosts (1.257641ms) ✔ isLoopbackHost: recognizes loopback hosts (1.301749ms)
✔ isLoopbackHost: rejects non-loopback hosts (4.586746ms) ✔ isLoopbackHost: rejects non-loopback hosts (4.395006ms)
✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.074877ms) ✔ startServer: refuses a non-loopback host with ConfigError, never opens a socket (3.037957ms)
✔ startServer: serves page, healthz, and a rescanning /api/board (34.39639ms) ✔ startServer: serves page, healthz, and a rescanning /api/board (32.7969ms)
✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (6.300202ms) ✔ startServer: a seatsDir registration overrides the row and index.registered reflects it (5.757893ms)
✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.380365ms) ✔ startServer: /api/board returns 500 JSON with an error field when scan throws (2.289878ms)
✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (55.462546ms) ✔ CLI: serve refuses a non-loopback host with exit 2 and a refused: message (48.755191ms)
✔ CLI: serve rejects a non-numeric --port with exit 2 (54.15464ms) ✔ CLI: serve rejects a non-numeric --port with exit 2 (48.672064ms)
✔ CLI: scan still works after the async cli refactor (48.467479ms) ✔ CLI: scan still works after the async cli refactor (53.128023ms)
✔ CLI: live serve prints its URL and answers /healthz (60.472536ms) ✔ CLI: live serve prints its URL and answers /healthz (58.19203ms)
✔ page.html: esc() escapes every HTML-significant character (0.615927ms) ✔ page.html: esc() escapes every HTML-significant character (0.583626ms)
✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (8.194124ms) ✔ POST /api/seen marks a row; GET /api/board still shows it seen; seen:false clears it (7.824105ms)
✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (2.489995ms) ✔ POST /api/seen without a JSON content-type returns 400 and does not write a mark (1.849175ms)
✔ POST /api/seen with invalid JSON returns 400 (2.794772ms) ✔ POST /api/seen with invalid JSON returns 400 (2.650642ms)
✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (2.07639ms) ✔ POST /api/seen with a body over 4096 bytes returns 400 (or resets the connection) and writes no mark (1.955345ms)
✔ POST /api/seen with a missing agent returns 400 (1.345416ms) ✔ POST /api/seen with a missing agent returns 400 (1.288633ms)
✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.686153ms) ✔ POST /api/board returns 405; PUT /api/seen returns 405 (1.62688ms)
✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (54.281161ms) ✔ CLI: scan --print marks a seen row with 's' and the summary line ends with 'N seen)' (53.682982ms)
✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.320625ms) ✔ page.html: seenControl() escapes rec.project/agent/lastActivity, and the POST uses a JSON content-type (0.306082ms)
✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.264823ms) ✔ page.html: has a collapsed Seen section that lists seen rows with the shared row builder (0.272848ms)
✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.169369ms) ✔ page.html: each project has a Hide seen checkbox (default on) beside Hide offline, with a hidden-count note (0.163563ms)
✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.128358ms) ✔ page.html: a project header reads "N of N" only while a checkbox hides rows (0.128431ms)
✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.308994ms) ✔ page.html: every row shows Task and Active project, derived or the word unknown, with the workspace in the detail (0.267941ms)
✔ 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.560554ms) ✔ 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.543486ms)
✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (27.75916ms) ✔ POST /api/reply: runs agent-send.sh with -s from the registration, -S <host>:control-board, -m text plus the fixed trailer, no -L on the default socket, MOSAIC_TMUX_SOCKET stripped; answers delivered with the exit code and both streams (26.420253ms)
✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (25.018106ms) ✔ POST /api/reply: a registration with a tmux socket adds -L <socket> (27.128876ms)
✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (26.681206ms) ✔ POST /api/reply: a non-zero tool exit is a 200 with delivered false, the exit code and the stderr verbatim (24.803785ms)
✔ 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.406945ms) ✔ 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.134004ms)
✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.315379ms) ✔ POST /api/reply: a missing agent-send.sh is a 500 with the path in the error, not a crash (4.289581ms)
✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.152417ms) ✔ replyToRow: DEFAULT_AGENT_SEND is the repository's tools/tmux/agent-send.sh and it is executable (0.16247ms)
✔ 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.568856ms) ✔ 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.568982ms)
✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.500947ms) ✔ startServer: /api/board carries taskSetBy from a live registration and null for the derived rows (2.348384ms)
✔ 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.300847ms) ✔ 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.300945ms)
✔ 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.930828ms) ✔ 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.60865ms)
✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (29.300207ms) ✔ Host/Origin guard: loopback names on this port are accepted, with or without a same-origin Origin (36.209428ms)
✔ 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.619576ms) ✔ 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.451935ms)
✔ every refusal code the reader can raise has an HTTP status (0.918207ms) ✔ every refusal code the reader can raise has an HTTP status (0.909668ms)
✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (45.444967ms) ✔ conversation routes: catalogue, first page, next page and follow over HTTP; refusals map to 4xx with their code; nothing is written (48.522532ms)
ℹ tests 124 ℹ tests 124
ℹ suites 0 ℹ suites 0
ℹ pass 124 ℹ pass 124
@@ -129,4 +129,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 621.068041 ℹ duration_ms 622.509231
+164 -155
View File
@@ -1,160 +1,169 @@
✔ W1: two processes acquire the same pair at once; exactly one claim (477.086964ms) ✔ W1: two processes acquire the same pair at once; exactly one claim (359.377469ms)
✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (37.843206ms) ✔ W1: two writers publish the same revision at once: one wins, the other gets null, the winner's record stays (33.328804ms)
✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (19.247751ms) ✔ W1: a revision name appears only after its bytes are synced; before that, only a temp file exists (18.46367ms)
✔ W2: acquire while a claim is reserved or active refuses already-active (368.84834ms) ✔ W2: acquire while a claim is reserved or active refuses already-active (363.910358ms)
✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (410.911345ms) ✔ W3: acquire while stopping, uncertain, or stopped without proof refuses unsafe-replacement (388.698692ms)
✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (280.706297ms) ✔ W4: same session with another seat tuple, and the reverse, both refuse; a loser on the seat key closes it no-unit (269.874367ms)
✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (217.122101ms) ✔ W4: a hard link of one session under another seat is the same session: the second controller refuses already-active and launches nothing (285.475085ms)
✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (199.76267ms) ✔ W4: a copy of one session under another seat is the same session: the second controller refuses already-active and launches nothing (284.470985ms)
✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.223489ms) ✔ W4: a session header ID that changes after construction refuses target; nothing is claimed or launched (2.493769ms)
✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (9978.92205ms) ✔ W5: SIGKILL between every publication barrier of acquire and transition; restart never finds two holders or a lost claim (10296.932811ms)
✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (32447.817836ms) ✔ W5: SIGKILL between every publication barrier of release; restart finishes or holds the release (28433.437816ms)
✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (328.138189ms) ✔ W6: controller killed mid-turn while the engine lives; restart is uncertain, no launch, prompts refuse (253.554797ms)
✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (193.375637ms) ✔ W12: a live owner paused with SIGSTOP; a second controller refuses already-active and changes nothing (142.395586ms)
✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (647.531117ms) ✔ W13: crash after the engine spawns, before active; restart finds the live unit: uncertain, no second spawn, force stop only (382.889006ms)
✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (307.626437ms) ✔ W14: crash after reservation, before the spawn marker: stopped with a no-unit observation; the pair is free (272.398595ms)
✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (442.883523ms) ✔ W20: crash after the spawn marker, scope collected; uncertain in both runs, the marker is copied, no launch until a boot proof (345.056704ms)
✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (120.786161ms) ✔ W15: crash between the two keys during release; restart finishes it under the same claim ID (104.150164ms)
✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (199.28518ms) ✔ W7: recorded boot ID differs on the same machine: stopped with a boot proof; open tool calls become uncertain (168.293925ms)
✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (510.669432ms) ✔ W8: resume after a proven stop with the same pins: new claim ID, generation +1, same conversation, branch and leaf (274.310046ms)
✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (978.317356ms) ✔ W9: resume with a changed binary, argv digest, branch or leaf is refused and the claim is unchanged (778.563954ms)
✔ W11: the controller writes no session file; only the fake engine's own appends appear (384.697568ms) ✔ W11: the controller writes no session file; only the fake engine's own appends appear (185.423529ms)
✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (93.004349ms) ✔ W16: a highest revision that won't parse holds the pair uncertain; the older stopped revision is not reused (92.977508ms)
✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (93.796846ms) ✔ W17: a claim root copied from another host refuses foreign-host and promotes nothing (76.575557ms)
✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (3.873228ms) ✔ G1: a session path or claim root under .pi/state, ~/.claude, the data root or a registration refuses at construction (4.590669ms)
✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.11013ms) ✔ G2: a symlink inside the fixture root to a live session file is refused by the real-path check (1.149257ms)
✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.892311ms) ✔ G3: a fixture path swapped for a live path after construction is refused at bind (1.74318ms)
✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (2760.86431ms) ✔ K1: force stop kills a tool child that called setsid; stopped with a verified proof (3050.091635ms)
✔ K2: K1 on the process-group fallback ends uncertain, never stopped (630.859964ms) ✔ K2: K1 on the process-group fallback ends uncertain, never stopped (548.184536ms)
✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2695.393731ms) ✔ K3: SIGTERM acknowledged while a member lives: stopping until the kill phase, never stopped from TERM (2802.363959ms)
✔ K4: two engines; force stop one; the other survives by independent observation (4759.634246ms) ✔ K4: two engines; force stop one; the other survives by independent observation (5084.349885ms)
✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2495.83911ms) ✔ K5: a stop during a tool call leaves the effect uncertain, and it is shown (2496.187397ms)
✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2572.283036ms) ✔ K12: a member forking in a loop: the freeze stops it, enumeration is complete, populated 0 after cgroup.kill (2576.331714ms)
✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2503.7825ms) ✔ K13: a member writing its pid into another cgroup is refused by the namespace; the kill is complete (2344.11219ms)
✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (5381.751999ms) ✔ K15: the shim gone, engine/cgroup.events unreadable, or the engine cgroup missing: evidence unavailable, not empty; uncertain (4908.867845ms)
✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (817.464529ms) ✔ K10: controller killed between the TERM and kill phases: restart checks the invocation ID and re-runs from TERM for the same stop (767.49496ms)
✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (622.709407ms) ✔ K11: controller killed after the confirmation is recorded, before TERM: restart checks the invocation ID and re-runs from TERM for the same stop (598.437478ms)
✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (529.469371ms) ✔ K14: a unit with the recorded name but another invocation ID: evidence unavailable, no signals, uncertain (447.999699ms)
✔ K6: recover without proof, without confirmation, or with changed pins is refused (1132.171151ms) ✔ K6: recover without proof, without confirmation, or with changed pins is refused (766.506912ms)
✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (411.918412ms) ✔ K7: recover after proof, then launch: new claim and execution, generation +1, same leaf; the cancelled prompt is not replayed (422.600411ms)
✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (746.116766ms) ✔ K8: an engine that loads another leaf on resume is refused before admission; it stays claimed until a proven stop (578.979806ms)
✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3445.858035ms) ✔ K9: an interrupt that never settles stays uncertain; force stop stays available; takeover is refused while fenced (3319.848238ms)
✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (42.281682ms) ✔ K16: a claim from another machine ID refuses foreign-host; no boot proof is issued (56.082477ms)
✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (507.081862ms) ✔ K17: two launcher calls with one eligibility record: one launch, the other refuses, no second engine (398.032896ms)
✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (390.990071ms) ✔ K18: the leaf changes after eligibility: launch refused; the reservation stays until released with proof (379.039059ms)
✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (474.385289ms) ✔ K19: a scope launched with only the engine environment still reaches the user manager; the engine sees no other names (61.955645ms)
✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (210.910934ms) ✔ S1: `/goal x`, with leading spaces or a tab, refuses text-policy at admission; zero engine bytes (183.172361ms)
✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (205.02606ms) ✔ S2: every prefix pinned Pi interprets is refused, from the list the code uses; the rest reach the engine exactly (321.103419ms)
✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (292.731024ms) ✔ S3: `/goal` on the second line is pinned from the source: Pi checks only index 0, so it is admitted and sent exactly (202.586883ms)
✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (215.429587ms) ✔ S4: a `/` left in the composer is cleared when control transfers and returns; the next submit sends only the new text (286.026448ms)
✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.556421ms) ✔ S5: an observer terminal gets a paste then Enter, as send-message.sh does: not admitted: controller, nothing sent (201.937752ms)
✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (215.469925ms) ✔ S6: a mediated-shaped registration (no tmux) passed to the board's replyToRow: 409 no tmux session; exec never runs (0.440232ms)
✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (319.283723ms) ✔ S7: ESC, bracketed-paste markers and U+2028/U+2029 travel as one JSON string; the engine receives the exact text in one record (191.66305ms)
✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (200.663647ms) ✔ P3: a Pi confirm, select, input or editor dialog is shown disabled with a reason and never answered (310.844897ms)
✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (204.301153ms) ✔ E1: send, ack, user, toolCall, toolResult, final answer: shown once, no refresh, draft and reading position kept (304.129242ms)
✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (221.684003ms) ✔ E2: U+2028, U+2029 inside JSON strings and CRLF line ends each parse as one record, on the splitter and through the controller (273.873123ms)
✔ 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 (228.565966ms) ✔ E3: a multipart final, two blocks, null request correlation and duplicate delivery (249.26308ms)
✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.969327ms) ✔ 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 (233.10852ms)
✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (408.146813ms) ✔ E4: a gap or a new epoch also reconciles; nothing is concatenated across a gap (10.562087ms)
✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (219.412379ms) ✔ E5: an unknown native event gives no client event; evidence records its type and bytes; the terminal count goes up (228.685521ms)
✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (242.362495ms) ✔ E6: a tool result delayed across a pause and a reconnect is reconciled without a manual refresh (263.866374ms)
✔ terminal: engine control characters are made visible; a lost connection refuses submit (191.926822ms) ✔ E7: the terminal renders the same stream as the library client, as observer and then as controller, and submits only as controller (354.599299ms)
✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.410826ms) ✔ terminal: engine control characters are made visible; a lost connection refuses submit (232.217026ms)
✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.247826ms) ✔ terminal: outcome unknown is shown as such, with no resend offer, and nothing is resent (0.426386ms)
✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.29839ms) ✔ terminal: text after Enter in the same input chunk starts the next message; it never joins the one submitted (0.279896ms)
✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.105483ms) ✔ terminal: a paste-start marker split right after its ESC still opens the paste; the Enter inside it never submits (0.319455ms)
✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (217.236622ms) ✔ terminal: Ctrl-T then Enter in one chunk is judged after the takeover, as if typed one key at a time (Filbert F2, #1507) (0.490795ms)
✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (520.226617ms) ✔ terminal: input held behind Ctrl-T waits for that takeover while an earlier action is still pending (Darkwing T1 on #1522) (31.062222ms)
✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (311.119802ms) ✔ terminal: an action that throws still releases the input held behind it, in order, then rethrows (5.78363ms)
✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (866.507043ms) ✔ terminal: after an action throws, later input still runs; input() puts the error in the status line (Filbert N1 on #1522) (0.388833ms)
✔ H4: self-takeover is refused (204.501347ms) ✔ terminal: input() reports an error when it happens, so it never overwrites a later status; held input's promise doesn't carry the holder's error (Filbert N4 on #1522) (0.402692ms)
✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (447.543165ms) ✔ terminal: invisible and bidi characters are made visible; head, status and notice lines stay one line (0.113939ms)
✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1285.535233ms) ✔ every record these fixtures produced is a valid CHAT-01 record (E5: no record fails the schema) (224.218939ms)
✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (218.518959ms) ✔ H1: two takeovers with the same expected generation: one wins, +1; the other refuses generation (389.777116ms)
✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (810.936365ms) ✔ H2: the old controller's prompt after a takeover commits is refused with zero engine bytes (298.231563ms)
✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (330.683336ms) ✔ H3: a takeover while a prompt holds the dispatch lock: written under the old actor, or refused; never both (878.173246ms)
✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (179.099307ms) ✔ H4: self-takeover is refused (281.507485ms)
✔ H13: a retry with the same request ID and different text is refused (175.414126ms) ✔ H9: Interrupt racing a prompt's dispatch: before the write, dispatch-refused and no-turn; after, §3 rules (541.231998ms)
✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (598.452038ms) ✔ H10: Interrupt and force stop together: one stop chain, force stop supersedes (1273.826772ms)
✔ H15: a revoked connection's command is refused; the revocation fence holds (276.572706ms) ✔ H10: an overlap during the pause before the abort: no abort, the stop ends uncertain (187.264249ms)
✔ H16: a second controller for the same session refuses already-active; the first is untouched (194.198551ms) ✔ H10: a no-turn Interrupt lifts only its own fence; admission stays closed under force stop, overlap or revocation (681.954522ms)
✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (824.830099ms) ✔ H11: the controller disconnects mid-turn: work continues, the claim is unchanged, control stays put (360.958917ms)
✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (899.906199ms) ✔ H12: an exact retry after reconnecting to the same incarnation returns the same receipt; one dispatch (179.335026ms)
✔ H18: two prompts before any native output: the second refuses busy; one engine write (207.591122ms) ✔ H13: a retry with the same request ID and different text is refused (214.621381ms)
✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (353.488159ms) ✔ H14: late stdout from the old engine after a replacement is dropped by incarnation, counted, never rendered (669.427202ms)
✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (0.622545ms) ✔ H15: a revoked connection's command is refused; the revocation fence holds (324.988382ms)
✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (651.247301ms) ✔ H16: a second controller for the same session refuses already-active; the first is untouched (252.975247ms)
✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (547.326231ms) ✔ H10: a second force stop while the first escalation runs refuses fenced; one escalation, and the claim records only the first stop's phases (946.156333ms)
✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (725.273442ms) ✔ a force stop whose fence throws leaves no escalation flag behind, so the next force stop runs (Darkwing F2, #1507) (384.175884ms)
✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (3026.548403ms) ✔ H17: a confirmation reused, answered from another connection, or used after the stop changed is refused (790.562657ms)
✔ H23: requests pending at a restart are not resent; each shows outcome unknown (669.354531ms) ✔ H18: two prompts before any native output: the second refuses busy; one engine write (213.007221ms)
✔ a plain conversation: catalogue row, one page, CHAT-01 records (7.522907ms) ✔ H19: the pipe fails mid-line under a large prompt: delivery-unknown transport-unknown, poisoned, no later write (354.983148ms)
✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (1.848223ms) ✔ H19: the link itself never writes again after an unknown outcome, whoever calls it (1.133225ms)
✔ F1: a malformed line is an unavailable part at its position, and reading continues (2.044985ms) ✔ H19: the controller dies mid-write of a large line: after restart the outcome is unknown and nothing is resent (672.737607ms)
✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.975236ms) ✔ H20: the line is written but the ack is lost when the controller dies: orphan, outcome unknown, nothing resent (571.854496ms)
✔ F1: an unreadable fork is never merged into another branch's history (1.92965ms) ✔ H21: a retry of the exact request with the old token after a crash is stale-incarnation; no second write (766.474869ms)
✔ F1: a follow stays on its branch when the next entry's parent is unreadable (3.307045ms) ✔ H22: after H21 and a valid recovery, a new request with the new token is admitted (2921.745089ms)
✔ F1: a file whose entries are all unreadable shows a notice per line (1.344609ms) ✔ H23: requests pending at a restart are not resent; each shows outcome unknown (784.232685ms)
✔ F2: a truncated trailing line marks the view incomplete, not an error (2.117891ms) ✔ a plain conversation: catalogue row, one page, CHAT-01 records (10.200413ms)
✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (6.007126ms) ✔ native entries map to blocks: tools, thinking, bash, notices, ids that do not fit (2.191269ms)
✔ F3: a replaced file (new inode) refuses old cursors with reconcile (5.955883ms) ✔ F1: a malformed line is an unavailable part at its position, and reading continues (1.732908ms)
✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (5.84029ms) ✔ F1: a missing parent stops the history with a notice that names the unreadable lines (2.436723ms)
✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.009923ms) ✔ F1: an unreadable fork is never merged into another branch's history (1.55108ms)
✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (9.34471ms) ✔ F1: a follow stays on its branch when the next entry's parent is unreadable (1.945379ms)
✔ F7: a symlinked file and a symlinked directory component are refused, never opened (7.62926ms) ✔ F1: a file whose entries are all unreadable shows a notice per line (0.772242ms)
✔ F8: a file swapped for a symlink after the catalogue is refused (1.867437ms) ✔ F2: a truncated trailing line marks the view incomplete, not an error (1.20009ms)
✔ F9: registrations never add or redirect a root (1.373913ms) ✔ pagination: 100 parts, then the rest; parts concatenate to the whole branch (3.62995ms)
✔ F10: a header cwd naming another project is refused (3.03181ms) ✔ F3: a replaced file (new inode) refuses old cursors with reconcile (4.026251ms)
✔ F11: parentSession renders with a marker and the parent is never opened (0.704383ms) ✔ F4: a same-inode rewrite of the prefix refuses old cursors with reconcile (4.677737ms)
✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.582929ms) ✔ F5: growth between pages keeps the epoch and the page stops at the pinned length (5.242173ms)
✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.130198ms) ✔ F6: unknown, foreign and expired cursors refuse and leave the cursor usable (7.719936ms)
✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.109862ms) ✔ F7: a symlinked file and a symlinked directory component are refused, never opened (9.703355ms)
✔ F13: compaction is a marker in place, then the retained content (0.617581ms) ✔ F8: a file swapped for a symlink after the catalogue is refused (3.123398ms)
✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (509.870195ms) ✔ F9: registrations never add or redirect a root (1.979044ms)
✔ fragments never cut a surrogate pair and keep an empty string (4.365936ms) ✔ F10: a header cwd naming another project is refused (4.129598ms)
✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.369863ms) ✔ F11: parentSession renders with a marker and the parent is never opened (1.188308ms)
✔ unknown conversations, empty files and non-Pi files refuse (2.036043ms) ✔ F12: two leaves: the default leaf is shown and the other branch reads alone (4.379712ms)
✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (0.950981ms) ✔ F12: a follow refuses when an appended duplicate id changes the branch's earlier parts (2.106846ms)
✔ a seat directory without search permission refuses that root, not the catalogue (1.775213ms) ✔ F12: a second root (Pi's resetLeaf) starts its own branch (1.443953ms)
✔ every page and cursor is a valid CHAT-01 record (809.072625ms) ✔ F13: compaction is a marker in place, then the retained content (0.834612ms)
✔ the engine pin holds for the installed package (2.202385ms) ✔ F14: long strings split into fragments and parts, reassemble exactly, and pages respect the byte cap (545.028141ms)
✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (267.632344ms) ✔ fragments never cut a surrogate pair and keep an empty string (4.819554ms)
✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (262.448955ms) ✔ F15: a Claude seat is an unsupported-harness placeholder whose directory is never read (1.532997ms)
✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (766.717715ms) ✔ unknown conversations, empty files and non-Pi files refuse (2.227861ms)
✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (446.523598ms) ✔ an unreadable file or root inside the roots is refused per row, not a failed catalogue (1.045168ms)
✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (256.450936ms) ✔ a seat directory without search permission refuses that root, not the catalogue (1.925268ms)
✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (22.687863ms) ✔ every page and cursor is a valid CHAT-01 record (806.800026ms)
✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (386.692522ms) ✔ the engine pin holds for the installed package (2.653271ms)
✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (193.528679ms) ✔ pinned Pi, sealed and without credentials, answers the controller's commands with the shapes the fake models (296.340457ms)
✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (301.720364ms) ✔ sealed, pinned Pi ignores a trusted project's .pi resources; --approve past the seal would load them, and checkSeal refuses it (Filbert F2 on #1522) (580.767636ms)
✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (718.122152ms) ✔ pinned Pi appends thinking_level_change at start when the branch lacks one, so the leaf moves (K8 then fails closed) (245.397943ms)
✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1878.204406ms) ✔ N25: ordinary Interrupt reconciles; a non-empty queue_update in the window is O5 (596.98809ms)
✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (183.386995ms) ✔ N1: an extension's follow-up queued after the fence is cleared before any abort; O5, Unknown (442.973028ms)
✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (259.106542ms) ✔ N1: a follow-up queued before the fence is O5 at once; the Interrupt refuses fenced (231.583496ms)
✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (187.803775ms) ✔ N2: with abort first, the fake runs the external item (the ordering guard has teeth) (21.201669ms)
✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (240.606876ms) ✔ N3: the fence lands in preflight, preflight errors, no run: failed, No run, uncertain (355.566202ms)
✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (364.999405ms) ✔ N4: the ack arrives after the first abort and a run starts: clear and abort again; Interrupted (290.291879ms)
✔ N10: fake conformance (33.333721ms) ✔ N5: an input handler takes the prompt: ack, no run, delivery-unknown handled-without-run (379.339574ms)
✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (422.040983ms) ✔ N6: an extension queues between clear_queue and abort: O5 and O6, Unknown (532.60621ms)
✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (268.737656ms) ✔ N7: clear_queue times out: no abort, nativeQueue unknown, force stop still ends it (1929.104949ms)
✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (279.646287ms) ✔ N7: clear_queue answers an error: no abort, nativeQueue unknown, the link not poisoned (211.450644ms)
✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (362.146811ms) ✔ N8: an extension prompt starts a run during Mosaic preflight; the losing settle is O3 (243.00463ms)
✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (496.549872ms) ✔ N9: a run that started before the fence and ends aborted: failed interrupted, Interrupted (218.445843ms)
✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (363.792931ms) ✔ N9: decision 34: a run that ends aborted with no stop in progress: aborted-without-stop, uncertain, outcome unknown (259.280416ms)
✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (214.329359ms) ✔ N9: an aborted that lands after the fence but before any abort is written: aborted-without-stop, Unknown (439.538163ms)
✔ N17: the run fails on its own during the exchange: failed, Failed on its own (373.398495ms) ✔ N10: fake conformance (32.625402ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (800.518546ms) ✔ N11: the run fails before any user message_start: delivery-unknown ack-without-start, never failed (444.021683ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (623.374789ms) ✔ N12: input that starts a run after the final empty clear is O1 and not part of the stop's proof (324.9118ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (588.079734ms) ✔ N13: agent_start with no slot held is O1; a later prompt refuses with zero engine bytes (310.151067ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (243.209329ms) ✔ N14: the run completes while clear_queue is in flight: finished, Completed first, uncertain (383.659084ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (245.190658ms) ✔ N14: the run completes after the abort is written, before Pi applies it: finished, never relabelled (395.918821ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (236.863088ms) ✔ N15: the fence lands in preflight, then an input handler takes it: handled-without-run, No run (357.274182ms)
✔ 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 (260.673316ms) ✔ N16: Interrupt with no slot and no run refuses no-turn: no stop, no bytes, admission open (216.272632ms)
ℹ tests 152 ✔ N17: the run fails on its own during the exchange: failed, Failed on its own (372.258093ms)
✔ N18: no final assistant message_end, or a lost line: working stays working; before working, transport-unknown (807.464808ms)
✔ N19: a losing extension prompt settles inside the Mosaic run before its user message: O3, run-overlap (470.065465ms)
✔ N20: an extension triggerTurn during Mosaic preflight starts first; while streaming it queues with no signal (476.174159ms)
✔ N21: a losing settle after the receipt settled finished is O2; the receipt stays finished (212.272242ms)
✔ N22: an agent-level custom message is dropped by the clear with no signal; evidence names the seal (201.480679ms)
✔ N23: a nextTurn message survives clear and abort and attaches to the next prompt, with no signal (209.403669ms)
✔ 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 (234.187048ms)
✔ N24b: the seal covers the engine command and environment: config can't name either, the env is built from names, and a mutated command is refused at bind (207.709795ms)
ℹ tests 161
ℹ suites 0 ℹ suites 0
ℹ pass 152 ℹ pass 161
ℹ fail 0 ℹ fail 0
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 50646.61675 ℹ duration_ms 46015.209142
+179 -179
View File
@@ -1,181 +1,181 @@
✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.061117ms) ✔ approvals: a request is validated before anything is posted; the rendering shows names and never ids (3.081039ms)
✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.767093ms) ✔ approvals: the ledger is appended and folded into open requests with bind and approval states (1.450448ms)
✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.615603ms) ✔ approvals: a reply approves only when it points at a request, says exactly approve, and comes from a listed approver once (0.539404ms)
✔ approvals: a button approves only on its own request message with the matching custom id (0.478036ms) ✔ approvals: a button approves only on its own request message with the matching custom id (0.486838ms)
✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (16.487303ms) ✔ approvals flow: a turn that opened a request posts the message with the button, records it, binds it, and both approvers approve (19.212333ms)
✔ 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.236326ms) ✔ 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.226047ms)
✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (7.83515ms) ✔ approvals flow: an invalid request from the model, a refused post, and no api client are recorded and post nothing (6.793303ms)
✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.795146ms) ✔ approvals flow: start retries a bind and an approval left as unknown, under their original keys (2.348484ms)
✔ authorize: open channel, listed user (4.438211ms) ✔ authorize: open channel, listed user (2.034771ms)
✔ authorize: wrong guild (0.234954ms) ✔ authorize: wrong guild (0.197565ms)
✔ authorize: no guild (DM) (0.297955ms) ✔ authorize: no guild (DM) (0.306546ms)
✔ authorize: unlisted channel (0.180274ms) ✔ authorize: unlisted channel (0.195089ms)
✔ authorize: unknown channel, no info (0.281968ms) ✔ authorize: unknown channel, no info (0.278659ms)
✔ authorize: thread of listed parent (0.188408ms) ✔ authorize: thread of listed parent (0.20402ms)
✔ authorize: thread of unlisted parent (0.277177ms) ✔ authorize: thread of unlisted parent (0.248401ms)
✔ authorize: text channel that is not a thread and not listed (0.135711ms) ✔ authorize: text channel that is not a thread and not listed (0.135501ms)
✔ authorize: unlisted user (1.317345ms) ✔ authorize: unlisted user (1.223655ms)
✔ authorize: no author (0.494804ms) ✔ authorize: no author (0.567415ms)
✔ authorize: bot author (listed id, bot flag) (0.229594ms) ✔ authorize: bot author (listed id, bot flag) (0.23823ms)
✔ authorize: system author (0.324529ms) ✔ authorize: system author (0.329115ms)
✔ authorize: the bot itself (0.080666ms) ✔ authorize: the bot itself (0.090371ms)
✔ authorize: webhook (0.567864ms) ✔ authorize: webhook (0.116421ms)
✔ authorize: mention channel without mention (0.132158ms) ✔ authorize: mention channel without mention (1.030567ms)
✔ authorize: mention channel with bot mention (0.119555ms) ✔ authorize: mention channel with bot mention (0.138834ms)
✔ authorize: mention channel with @everyone only (0.264226ms) ✔ authorize: mention channel with @everyone only (0.244925ms)
✔ authorize: mention channel mentioning someone else (0.073763ms) ✔ authorize: mention channel mentioning someone else (0.125004ms)
✔ authorize: mention channel, content says @bot but mentions empty (0.07773ms) ✔ authorize: mention channel, content says @bot but mentions empty (0.115784ms)
✔ authorize: private thread under mention channel, mentioned (0.077728ms) ✔ authorize: private thread under mention channel, mentioned (0.370489ms)
✔ authorize: private thread under mention channel, not mentioned (0.063982ms) ✔ authorize: private thread under mention channel, not mentioned (0.0842ms)
✔ authorize: thread in another guild per channel info (0.068838ms) ✔ authorize: thread in another guild per channel info (0.08482ms)
✔ authorize: not an object (0.064676ms) ✔ authorize: not an object (0.063472ms)
✔ authorize: no id (0.050024ms) ✔ authorize: no id (0.067893ms)
✔ authorize: oversize content is accepted and flagged (0.060955ms) ✔ authorize: oversize content is accepted and flagged (0.072189ms)
✔ authorize: exactly the limit is not oversize (0.038377ms) ✔ authorize: exactly the limit is not oversize (0.062234ms)
✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.433661ms) ✔ authorize: a user's channel allowlist drops them outside it, threads count as the parent, others are unaffected (0.444603ms)
✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.211684ms) ✔ authorize: order puts wrong guild before user, and user before channel (no channel lookup for strangers) (0.136339ms)
✔ binding: a complete binding validates and is frozen (2.713898ms) ✔ binding: a complete binding validates and is frozen (4.514209ms)
✔ binding: unknown key, missing field, wrong type refuse with exit 2 (3.959445ms) ✔ binding: unknown key, missing field, wrong type refuse with exit 2 (2.66757ms)
✔ binding: empty allowlists refuse (0.42288ms) ✔ binding: empty allowlists refuse (0.553791ms)
✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.510414ms) ✔ binding: a user's channel allowlist must be non-empty, listed and unique; absent means every listed channel (1.228209ms)
✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.56028ms) ✔ reloadDiff: reloadable keys are summarised by id; every fixed key refuses with exit 2 (1.918301ms)
✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.942119ms) ✔ binding: dmRecipient is optional, a snowflake, one of the listed users, and a fixed key (1.292749ms)
✔ binding: file must be 0600, regular, not a symlink (1.904793ms) ✔ binding: file must be 0600, regular, not a symlink (1.797933ms)
✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.618572ms) ✔ binding: token file mode, symlink, emptiness and shape are checked; token never appears in errors (1.463434ms)
✔ cli: check refuses a non-0600 token file with exit 2 before any network use (95.112607ms) ✔ cli: check refuses a non-0600 token file with exit 2 before any network use (94.273472ms)
✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (2.882373ms) ✔ context files: absolute paths, traversal, symlinks and out-of-repo targets refuse; in-repo files resolve (1.998433ms)
✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (324.375472ms) ✔ cli: check refuses a missing context file and a missing binding with exit 2; usage is exit 4 (302.76564ms)
✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (215.962601ms) ✔ cli: reload validates the file first (exit 2), then needs a live owner (exit 1); usage is exit 4 (199.69244ms)
✔ cli: run refuses when STOP is present, before any network use (130.511988ms) ✔ cli: run refuses when STOP is present, before any network use (124.63982ms)
✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.732355ms) ✔ binding: tools is optional, validated strictly, a fixed key for reload, and its roots are resolved against the data root (1.614976ms)
✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.188652ms) ✔ binding: a git key is validated at load and reaches the extension whole, and only on a writable root (1.05988ms)
✔ 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.059867ms) ✔ 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.951929ms)
✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (16.533069ms) ✔ delivery: refused and unknown outcomes are journaled; a later chunk is not sent after a failure (17.465003ms)
✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (1.394054ms) ✔ delivery: restart with an unknown entry re-sends the same nonce once and reconciles before accepting traffic (2.33533ms)
✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.059545ms) ✔ delivery: an unknown entry older than the dedupe window is marked refused, not re-sent; a still-unknown one refuses start (1.130995ms)
✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.278418ms) ✔ delivery: repeated unknown reconciliations never refresh the dedupe window; the original intent time decides (1.249969ms)
✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.785008ms) ✔ turn: a failed engine turn posts the fixed line, never model output, and writes a failed record (2.347004ms)
✔ turn: a second message during a turn is held by the engine, both get their own reply and record (32.205924ms) ✔ turn: a second message during a turn is held by the engine, both get their own reply and record (33.966768ms)
✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (2.018175ms) ✔ turn: a thread under a listed channel is answered in the thread; an unknown thread is looked up once (3.277186ms)
✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (1.07793ms) ✔ drop: an unlisted user gets silence and one drop line; no inbox entry, no REST call, no engine call (0.969178ms)
✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (0.911887ms) ✔ drop: an oversize message is accepted into the inbox, answered with the fixed line and journaled as a drop (1.000196ms)
✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.432734ms) ✔ restart: an inbox with three ids and a replay of the same three produces zero turns (42.244621ms)
✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (32.443071ms) ✔ stop: STOP present refuses start; STOP written while running refuses new turns and the current one finishes (31.720943ms)
✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.276205ms) ✔ ceiling: the ceiling plus one is refused and journaled; one fixed line per UTC day; a new day accepts again (5.445929ms)
✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.124916ms) ✔ ceiling: a burst arriving while turns are still running cannot queue past the ceiling (3.647123ms)
✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.308167ms) ✔ ceiling: a turn interrupted by a crash still counts after restart; admissions are durable (2.283507ms)
✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.251125ms) ✔ ceiling: the daily notice survives a same-day restart; one delivery attempt in total, even when the first attempt crashed mid-flight (4.164505ms)
✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (1.25404ms) ✔ duplicate: the same event delivered twice while the thread lookup is held yields one prompt, one admission and one reply (2.080797ms)
✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.496794ms) ✔ journal: no token-shaped string and no model output on the drop path reaches disk (0.388785ms)
✔ 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.467632ms) ✔ 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.259766ms)
✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (2.027341ms) ✔ receipt: Discord refusing the reaction leaves the turn intact and records ok false (1.846438ms)
✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (4.119314ms) ✔ reload: a new user is silent before and answered after; a removed channel goes silent; a lower ceiling applies at once (3.025452ms)
✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (4.968528ms) ✔ reload: a fixed key refuses with exit 2 and the old binding stays in force (0.945613ms)
✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (4.093585ms) ✔ tools: with a tools binding the turn record lists every read and its outcome; without one the field is null (3.908303ms)
✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.581971ms) ✔ context: the Discord block names the server, channels and modes, and states the rules from Q15 and Q16 (2.623828ms)
✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.869654ms) ✔ context: with tools the block names the roots, keeps file content as data, and says to state refusals plainly (0.757913ms)
✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.108748ms) ✔ context: a writable root adds the write rules and says a write is real only once Jason commits (2.061002ms)
✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.341709ms) ✔ context: the envelope is one bracketed line then the text; names cannot break the line (1.182448ms)
✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.476551ms) ✔ context: a git root swaps the terminal-commit line for the git verbs, and a vault root adds the id protocol (1.48004ms)
✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.314316ms) ✔ context: assembleContext concatenates files in launcher format and appends the block; sha256 is stable (1.465377ms)
✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.832442ms) ✔ context: splitReply keeps paragraphs together under the limit and splits long ones at lines, spaces, then hard (0.909559ms)
✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.866483ms) ✔ engine: buildPiArgs carries the fixed flags, engine settings, session dir and prompt file (2.455854ms)
✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (0.41256ms) ✔ engine: with tools, buildPiArgs turns pi's own tools off, loads the extension explicitly and allowlists exactly our three (1.255634ms)
✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (56.029123ms) ✔ engine: a run with tool turns settles once, on the answer, with every tool call in the result (49.034823ms)
✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (38.421229ms) ✔ engine: a run that ends on a tool-only turn fails the prompt as empty; a retried run settles on the real end (45.677101ms)
✔ engine: one prompt, one turn, text and usage come back (32.150553ms) ✔ engine: one prompt, one turn, text and usage come back (32.578224ms)
✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (332.641169ms) ✔ engine: a prompt while streaming is held until pi settles, then sent as its own run, and answered in order (329.393546ms)
✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (235.751447ms) ✔ engine: a held prompt that times out before pi settles fails on its own and is never sent (232.374139ms)
✔ engine: timeout sends abort and fails only that turn; the process stays (105.139637ms) ✔ engine: timeout sends abort and fails only that turn; the process stays (105.090744ms)
✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (228.086886ms) ✔ engine: tool events from a run that outlived its timeout never land in the next prompt's record (231.736524ms)
✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (135.450867ms) ✔ engine: a prompt after a turn that timed out before its agent_start waits for pi to settle instead of being refused (125.739998ms)
✔ 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.23065ms) ✔ 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.427403ms)
✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (614.382317ms) ✔ engine: a timed-out turn pi starts only after the grace never answers a later prompt (615.124756ms)
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.267878ms) ✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (early prompt response) (1.313447ms)
✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.480882ms) ✔ engine: late events of a run past its grace, before pi exits, answer nothing and nothing more is sent (late prompt response) (0.500103ms)
✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (425.419954ms) ✔ engine: a timed-out run pi did start outlives the grace; the next prompt goes out when it ends (428.708785ms)
✔ engine: a malformed JSONL line fails the turn, not the process (23.979553ms) ✔ engine: a malformed JSONL line fails the turn, not the process (27.22705ms)
✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.337302ms) ✔ engine: a turn that ends in error rejects with the error code; process exit fails pending turns (47.573585ms)
✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.488183ms) ✔ gateway: hello -> identify with intents, ready, heartbeat with jitter, ack (2.512369ms)
✔ gateway: missed ack closes the socket and resumes with the last sequence (1.739254ms) ✔ gateway: missed ack closes the socket and resumes with the last sequence (1.513556ms)
✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.751079ms) ✔ gateway: op 7 reconnect resumes; op 9 non-resumable re-identifies (0.430292ms)
✔ gateway: op 9 resumable resumes (0.355449ms) ✔ gateway: op 9 resumable resumes (0.362511ms)
✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.814585ms) ✔ gateway: close 4014 is fatal, reports the missing intent, never reconnects (0.950744ms)
✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.53415ms) ✔ gateway: 4004 and 4013 are fatal too; 1006 reconnects with identify when no session (0.529163ms)
✔ gateway: close() is final and unparseable frames are ignored (0.397126ms) ✔ gateway: close() is final and unparseable frames are ignored (0.743481ms)
✔ git: config validation is strict, needs write: true, a work tree and a private token file (68.353129ms) ✔ git: config validation is strict, needs write: true, a work tree and a private token file (80.124457ms)
✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (47.851387ms) ✔ git: the child environment drops every host git config, names one helper, and carries the token path only for origin (43.024752ms)
✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (94.180702ms) ✔ git: status reports the branch, ahead/behind and changed paths, and refuses off the named branch or mid-merge (63.196562ms)
✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.281882ms) ✔ git: parseStatus reads porcelain v2 including renames and conflicts (0.267156ms)
✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (87.170171ms) ✔ git: a commit stages exactly the named files, carries the seat author and the requester trailer, and pushes at once (75.95283ms)
✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (97.112654ms) ✔ git: commit refusals: message, paths, requester, nothing to commit, and an index that already holds other work (85.863999ms)
✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (94.925433ms) ✔ git: a commit whose push fails is still a commit, says so, and the next commit's push carries both (D6) (91.976349ms)
✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (210.685657ms) ✔ git: pull is fast-forward only; a diverged origin or dirty local files refuse with nothing merged (197.172243ms)
✔ git: push pushes the named branch only and reports up to date (75.691059ms) ✔ git: push pushes the named branch only and reports up to date (70.161556ms)
✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (87.339624ms) ✔ git: no token value or token path ever reaches a git argument list; outputs are masked and capped (92.70954ms)
✔ git: the credential helper answers get over https from a private file and nothing else (197.323202ms) ✔ git: the credential helper answers get over https from a private file and nothing else (211.880329ms)
✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (751.124043ms) ✔ git: the vault protocol validates before a commit, honours another owner's lock, reserves ids, and locks around writes (724.394004ms)
✔ lock: the claim is exclusive; a second start against a live owner refuses (5.01841ms) ✔ lock: the claim is exclusive; a second start against a live owner refuses (4.810224ms)
✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (79.953023ms) ✔ lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it (83.089733ms)
✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (0.800902ms) ✔ lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it (1.097574ms)
✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (2.668839ms) ✔ lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over (3.792137ms)
✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (39.552971ms) ✔ lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it (46.780073ms)
✔ 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 (301.652949ms) ✔ 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 (296.037764ms)
✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.599138ms) ✔ lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities (0.490814ms)
✔ lock: a process whose start marker or boot id cannot be read refuses to claim (28.147864ms) ✔ lock: a process whose start marker or boot id cannot be read refuses to claim (27.03662ms)
✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.525339ms) ✔ lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over (1.406834ms)
✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (48.503637ms) ✔ lock: four processes racing for the same binding; exactly one claims it and the others refuse (47.041024ms)
✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (146.638909ms) ✔ lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner (144.507832ms)
✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (96.227093ms) ✔ lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue (87.869235ms)
✔ notices: a kind is recorded per UTC day and found again (0.413961ms) ✔ notices: a kind is recorded per UTC day and found again (0.424756ms)
✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.040159ms) ✔ notify: the DM channel opens once, every send carries the nonce, and only the message id comes back (5.061268ms)
✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.517272ms) ✔ notify: refusals and unknowns surface as RestOutcome without ids; a refused send reopens the channel next time (2.175207ms)
✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.894822ms) ✔ notify: no dmRecipient, a non-0600 binding or token, or a missing binding refuse before any network use (1.537785ms)
✔ 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.534669ms) ✔ 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.091675ms)
✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (32.739742ms) ✔ recover: an operator STOP refuses with exit 3 and is never removed, whatever the lock says (41.402473ms)
✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (32.258484ms) ✔ recover: a brake written during the unlock wins; STOP stays with both lines and the start is refused (28.892318ms)
✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (74.709691ms) ✔ recover: a held binding refuses with exit 3 and writes no STOP: live owner, alive pid without verifiable identity, unreadable record (65.565161ms)
✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (674.149506ms) ✔ cli: recover exits 0 when ready, 3 behind a brake or a held binding, and run's own STOP refusal is 3 (664.943136ms)
✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (8.893063ms) ✔ rest: createMessage sends nonce, enforce_nonce, empty allowed_mentions and a soft reply reference (2.390487ms)
✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (3.289935ms) ✔ rest: 429 waits retry_after and retries; 4xx is refused; 5xx and socket errors are unknown (2.97225ms)
✔ rest: content and nonce limits are enforced locally; typing never throws (0.578211ms) ✔ rest: content and nonce limits are enforced locally; typing never throws (0.745505ms)
✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.766243ms) ✔ rest: react PUTs the encoded emoji on the inbound message for @me; 2xx is true, anything else is false and never throws (0.833698ms)
✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.383503ms) ✔ rest: createDm posts the recipient and resolves the channel id; 4xx refused, 5xx unknown, bad id never sent (1.367962ms)
✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.518356ms) ✔ setspark config: a bare https or loopback origin, a private key file, a principal (5.533771ms)
✔ setspark config: reaches the tools config and the binding as a fixed key (3.994465ms) ✔ setspark config: reaches the tools config and the binding as a fixed key (2.879168ms)
✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.523408ms) ✔ setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension (1.567929ms)
✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (1.479859ms) ✔ setspark config: approvers come from the binding's users, never from the binding's setspark key (2.519442ms)
✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (18.113285ms) ✔ setspark verbs: required_approvers go out as discord ids from names and come back as names (26.079152ms)
✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (8.297505ms) ✔ setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in (11.731332ms)
✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.297547ms) ✔ setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse (6.588171ms)
✔ setspark keys: read per call, one printable token per file, rotation without a restart (3.103093ms) ✔ setspark keys: read per call, one printable token per file, rotation without a restart (2.787692ms)
✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.455146ms) ✔ setspark idempotency keys: principal, turn id, call index; connector keys name a step (0.507426ms)
✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.041441ms) ✔ setspark http core: json in and out, bearer header, idempotency header, fixed user agent, no key anywhere else (2.036213ms)
✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1009.053659ms) ✔ setspark http core: error bodies become fixed refusals with code and the 409 fields; server text is data, cut (1011.362787ms)
✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.981369ms) ✔ setspark verbs: a setspark key enables the eight verbs and no counters (0.427291ms)
✔ 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.59881ms) ✔ 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.326911ms)
✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.329166ms) ✔ setspark verbs: no turn refuses every write before any request; bad arguments refuse before any request; reads still work (1.238285ms)
✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.217806ms) ✔ setspark verbs: renderRecord caps long output and hides the accepted snapshot (0.210112ms)
✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.626764ms) ✔ setspark api: bind, add_approval (button and reply) and get use integer request ids and the connector's keys (2.333518ms)
✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (3.419099ms) ✔ tools: config refuses a missing, symlinked, dotted, non-directory or duplicate root and bad limits (4.301345ms)
✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.60831ms) ✔ tools: every escape is refused with a fixed reason and nothing outside the root is read (3.938099ms)
✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.026763ms) ✔ tools: happy paths list, read a window, and search case-insensitively; dotfiles and symlinks never appear (5.360337ms)
✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (3.707762ms) ✔ tools: the tool set renders text for the model, records details for the journal, and enforces the per-run budget (4.51199ms)
✔ tools: listing and search caps hold (10.282751ms) ✔ tools: listing and search caps hold (11.549555ms)
✔ tools: credential shapes are caught; ordinary prose and ids are not (0.943132ms) ✔ tools: credential shapes are caught; ordinary prose and ids are not (0.910634ms)
✔ 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.419338ms) ✔ 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.70352ms)
✔ tools: an unreadable file under the root is skipped by search and refused by read (0.868225ms) ✔ tools: an unreadable file under the root is skipped by search and refused by read (1.793211ms)
✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (0.995424ms) ✔ tools: config accepts write: true only as a boolean, and enables the write tools only then (1.496735ms)
✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (4.213116ms) ✔ tools: every write outside the fence is refused before any byte lands, and no temp file remains (6.125267ms)
✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.597178ms) ✔ tools: write_file leaves the exact bytes, edit_file replaces one exact match, and the set renders the change as uncommitted (2.60861ms)
✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.282156ms) ✔ tools: a target that changed between the check and the rename is refused and the temp file is removed (1.286364ms)
✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (2.530552ms) ✔ web: config takes an https or loopback-http SearXNG base url and a bounded fetch cap (3.900582ms)
✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.209727ms) ✔ web: address rules refuse every private, loopback, link-local, mapped and multicast form (2.233099ms)
✔ 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.074646ms) ✔ 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.613642ms)
✔ 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.468695ms) ✔ 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.24281ms)
✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.248987ms) ✔ web: html to text drops scripts, styles and comments, decodes entities and keeps block breaks (0.220327ms)
✔ 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.702411ms) ✔ 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.593587ms)
✔ 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.555878ms) ✔ 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.237781ms)
ℹ tests 178 ℹ tests 178
ℹ suites 0 ℹ suites 0
ℹ pass 178 ℹ pass 178
@@ -183,4 +183,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 2645.723969 ℹ duration_ms 2606.762318
+59 -56
View File
@@ -1,61 +1,64 @@
✔ sessionModel: agent vars win, then the system's execution settings (9.866803ms) ✔ sessionModel: agent vars win, then the system's execution settings (12.460806ms)
✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.436208ms) ✔ a pi bundle: prompt, policy, tools and manifest, 0600 in a 0700 directory (4.803458ms)
✔ a claude-code bundle adds the wrapped gate hook and the MCP config (2.729439ms) ✔ a claude-code bundle adds the wrapped gate hook and the MCP config (3.183429ms)
✔ a bundle is written once: an existing file refuses (1.77934ms) ✔ a bundle is written once: an existing file refuses (2.558688ms)
✔ a path with a single quote can't go into the hook command (1.80468ms) ✔ a path with a single quote can't go into the hook command (5.575367ms)
✔ allow exits 0, a deny exits 2 with the reason on stderr (126.770406ms) ✔ allow exits 0, a deny exits 2 with the reason on stderr (120.182248ms)
✔ a missing or wrong policy, or a bad event, exits 2 (76.681201ms) ✔ a missing or wrong policy, or a bad event, exits 2 (92.907722ms)
✔ the bundle's wrapped command: a missing gate or node still blocks (1093.817326ms) ✔ the bundle's wrapped command: a missing gate or node still blocks (1098.912574ms)
✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (754.278903ms) ✔ claude: typed tools through MCP, the hook blocks, builtins outside --tools don't exist (784.493298ms)
✔ claude: the hook alone blocks a path outside the workspace (481.812812ms) ✔ claude: the hook alone blocks a path outside the workspace (475.438299ms)
✔ claude: a second turn resumes the first turn's session (713.762483ms) ✔ claude: a second turn resumes the first turn's session (761.131232ms)
✔ claude adapter: --restricted is always passed (4.950058ms) ✔ claude adapter: --restricted is always passed (5.633714ms)
✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (717.308696ms) ✔ claude: CLAUDE.md files and auto-memory don't reach the model; without --restricted they do (755.874661ms)
✔ claude: a missing hook or MCP file refuses before claude starts (7.466398ms) ✔ claude: a missing hook or MCP file refuses before claude starts (7.642982ms)
✔ pi: policy tools and typed tools pass, anything else is blocked (3.399466ms) ✔ pi: policy tools and typed tools pass, anything else is blocked (4.44314ms)
✔ claude: builtins map from pi names, typed tools need the mcp prefix (0.784748ms) ✔ claude: builtins map from pi names, typed tools need the mcp prefix (2.810102ms)
✔ file tool paths must resolve inside the workspace (0.998403ms) ✔ file tool paths must resolve inside the workspace (2.416083ms)
✔ pi's own path normalisation can't be used to step out (0.906289ms) ✔ pi's own path normalisation can't be used to step out (1.033877ms)
✔ a symlink inside the workspace that points out is outside (0.787366ms) ✔ a symlink inside the workspace that points out is outside (1.215724ms)
✔ a dangling symlink is refused at any depth, in both harnesses (2.581633ms) ✔ a dangling symlink is refused at any depth, in both harnesses (3.331492ms)
✔ read is checked under every spelling pi's read would open, in both harnesses (15.173931ms) ✔ read is checked under every spelling pi's read would open, in both harnesses (16.236489ms)
✔ other spellings cover directories, dangling links and pi's cwd (1.755587ms) ✔ other spellings cover directories, dangling links and pi's cwd (1.531911ms)
✔ claude path fields per tool (0.614355ms) ✔ a relative path climbs from the workspace's real path, in both harnesses (3.899281ms)
✔ glob patterns stay inside the workspace (0.669965ms) ✔ claude path fields per tool (0.621252ms)
✔ a path that can't be checked is blocked (0.330426ms) ✔ glob patterns stay inside the workspace (0.575952ms)
✔ initialize, ping and tools/list (43.946107ms) ✔ a path that can't be checked is blocked (0.348937ms)
✔ tools/call goes through the tool socket; a refusal is an isError result (39.418977ms) ✔ initialize, ping and tools/list (49.829524ms)
✔ unknown tools and methods are JSON-RPC errors and never reach the socket (28.163039ms) ✔ tools/call goes through the tool socket; a refusal is an isError result (34.967036ms)
✔ a missing argument is a usage error (33.807275ms) ✔ unknown tools and methods are JSON-RPC errors and never reach the socket (27.488498ms)
✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (357.982252ms) ✔ a missing argument is a usage error (37.772085ms)
✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (329.534992ms) ✔ pi: typed tools reach the socket, the gate blocks, agent_end writes the marker (366.795327ms)
✔ pi: a read is refused when pi would open another spelling outside (330.331356ms) ✔ pi: a write through a dangling symlink is blocked, and nothing appears outside (342.967992ms)
✔ pi: a missing extension refuses before any model call (7.207519ms) ✔ pi: a read is refused when pi would open another spelling outside (331.351008ms)
✔ pi: an extension without its configuration fails pi's start (262.623748ms) ✔ pi: a relative path climbs from the real path of a workspace behind a symlink (347.772096ms)
✔ founderCheck: founder variables, then a needed service without a usable token (1.361445ms) ✔ pi: a relative path climbs from the real path of a dataRoot behind a symlink (326.24484ms)
✔ turnRequest names the sender, class, reply and decision (0.286463ms) ✔ pi: a missing extension refuses before any model call (6.749538ms)
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (280.080623ms) ✔ pi: an extension without its configuration fails pi's start (278.617538ms)
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (131.852678ms) ✔ founderCheck: founder variables, then a needed service without a usable token (1.375374ms)
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (555.206373ms) ✔ turnRequest names the sender, class, reply and decision (0.20362ms)
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1350.970037ms) ✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (260.24884ms)
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (163.705924ms) ✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (109.649271ms)
✔ founder credentials stop before the claim (20) (181.182154ms) ✔ typed tools carry the runner's capability; launch goes to the host's launch socket (398.824797ms)
✔ a refused claim exits 21; an ended run's capability exits 22 (216.20297ms) ✔ a RESULT gets no automatic reply; failed turns reply with the reason (1319.410101ms)
✔ the launch ending under a running session exits 22 (150.079251ms) ✔ SIGTERM during a turn kills the turn's process group and still exits 0 (163.443065ms)
✔ a broker that stays unreachable exits 23 after brokerRetries polls (246.177626ms) ✔ founder credentials stop before the claim (20) (199.484693ms)
✔ a broker that is down at the claim exits 23, not 21 (143.792117ms) ✔ a refused claim exits 21; an ended run's capability exits 22 (208.332497ms)
✔ no capability, or a malformed one, on stdin exits 2 (260.79667ms) ✔ the launch ending under a running session exits 22 (144.453341ms)
✔ a missing or malformed policy exits 2 before the claim (260.249644ms) ✔ a broker that stays unreachable exits 23 after brokerRetries polls (247.951469ms)
✔ the PM gets launch, its task verbs and the reads (9.127299ms) ✔ a broker that is down at the claim exits 23, not 21 (86.773045ms)
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (3.041734ms) ✔ no capability, or a malformed one, on stdin exits 2 (190.450865ms)
✔ launch only when the business's launch block names the instance as launcher (1.525074ms) ✔ a missing or malformed policy exits 2 before the claim (120.443165ms)
✔ an action outside the instance's authority has no tool (1.464781ms) ✔ the PM gets launch, its task verbs and the reads (9.240713ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (9.215168ms) ✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (2.476487ms)
ℹ tests 53 ✔ launch only when the business's launch block names the instance as launcher (1.907133ms)
✔ an action outside the instance's authority has no tool (2.007447ms)
✔ callTool: one JSON line out, the result back, a refusal rejects (9.197691ms)
ℹ tests 56
ℹ suites 0 ℹ suites 0
ℹ pass 53 ℹ pass 56
ℹ fail 0 ℹ fail 0
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 10592.472255 ℹ duration_ms 10351.534481
+79 -79
View File
@@ -1,81 +1,81 @@
✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (114.71017ms) ✔ a raw token file, with or without one trailing newline, reaches curl only through the config stream (120.327844ms)
✔ the raw path accepts nothing else, and refuses before curl runs (363.007926ms) ✔ the raw path accepts nothing else, and refuses before curl runs (369.402648ms)
✔ the file checks still apply on the raw path: mode, symlink, missing, directory (304.628111ms) ✔ the file checks still apply on the raw path: mode, symlink, missing, directory (279.837232ms)
✔ the raw path base URL has no override (64.735925ms) ✔ the raw path base URL has no override (54.291872ms)
✔ the JSON path is unchanged, and JSON never falls through to the raw path (228.133678ms) ✔ the JSON path is unchanged, and JSON never falls through to the raw path (230.9248ms)
✔ a file that changes between the two reads refuses before curl runs, with or without a body (715.743095ms) ✔ a file that changes between the two reads refuses before curl runs, with or without a body (713.684199ms)
✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (689.540339ms) ✔ the token reaches no child environment, even with an inherited CFG or SHELLOPTS=allexport (694.027535ms)
✔ real helper GET HTTP 200 preserves exit 0 without credentials (14.872591ms) ✔ real helper GET HTTP 200 preserves exit 0 without credentials (13.070169ms)
✔ real helper POST HTTP 201 preserves exit 0 without credentials (10.438959ms) ✔ real helper POST HTTP 201 preserves exit 0 without credentials (13.083497ms)
✔ real helper GET HTTP 403 preserves exit 1 without credentials (9.544019ms) ✔ real helper GET HTTP 403 preserves exit 1 without credentials (9.033467ms)
✔ fixture git subjects only, follow-ups and three session kinds (241.298844ms) ✔ fixture git subjects only, follow-ups and three session kinds (162.480984ms)
✔ text and JSON carry same numbers, open and truncated title (308.52844ms) ✔ text and JSON carry same numbers, open and truncated title (216.533556ms)
✔ missing credentials exit 2, no-issues never calls API and shows unknown (280.299865ms) ✔ missing credentials exit 2, no-issues never calls API and shows unknown (187.42729ms)
✔ empty range gives no rows and zero totals (213.054503ms) ✔ empty range gives no rows and zero totals (153.553472ms)
✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (215.603367ms) ✔ inclusive UTC dates, first-line preamble only, role and seat boundaries (145.104879ms)
✔ close-only issue included, even median, missing metadata stays unknown (197.614777ms) ✔ close-only issue included, even median, missing metadata stays unknown (138.143192ms)
✔ unique commits but per-issue links count multiple tags once each (215.527408ms) ✔ unique commits but per-issue links count multiple tags once each (137.264651ms)
✔ page cap refuses rather than silently undercounting (201.211581ms) ✔ page cap refuses rather than silently undercounting (130.219584ms)
✔ bad API payload not JSON refuses (140.742625ms) ✔ bad API payload not JSON refuses (124.426361ms)
✔ bad API payload {} refuses (196.87563ms) ✔ bad API payload {} refuses (130.835828ms)
✔ bad API payload [{"number":1}] refuses (182.932775ms) ✔ bad API payload [{"number":1}] refuses (131.717421ms)
✔ partial or malformed session log refuses with location, not content (186.997309ms) ✔ partial or malformed session log refuses with location, not content (141.490599ms)
✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (213.14167ms) ✔ a U+2028 or U+2029 inside a session string is one line, not a malformed record (135.981775ms)
✔ no sessions is an empty table; symlink source refuses (291.100853ms) ✔ no sessions is an empty table; symlink source refuses (208.0173ms)
✔ reads only refactor even when another branch is checked out (226.626428ms) ✔ reads only refactor even when another branch is checked out (147.21053ms)
✔ invalid dates, reverse dates and duplicate options refuse (176.688993ms) ✔ invalid dates, reverse dates and duplicate options refuse (103.221367ms)
✔ T3 agent assignments do not count as human in Table 2 (219.617626ms) ✔ T3 agent assignments do not count as human in Table 2 (139.224294ms)
✔ preamble parsing and issue number boundaries (0.434196ms) ✔ preamble parsing and issue number boundaries (0.429217ms)
✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.143448ms) ✔ T3 header: agent, or board from control-board; anything short of the full header is human (0.141263ms)
✔ no closed issues with human messages means undefined ratio, not invented zero (0.19361ms) ✔ no closed issues with human messages means undefined ratio, not invented zero (0.183749ms)
✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (824.563293ms) ✔ T3: seat, archived, unmapped and Researcher threads count; imported, deleted and other-project threads do not (439.040684ms)
✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (1000.905234ms) ✔ T3: the default path is read from HOME and prints no path line; --no-t3 says so (755.195371ms)
✔ T3: a HOME with no database exits 1 and names --no-t3 (203.609674ms) ✔ T3: a HOME with no database exits 1 and names --no-t3 (140.301202ms)
✔ T3: a file that is not a database exits 1 and names --no-t3 (194.71429ms) ✔ T3: a file that is not a database exits 1 and names --no-t3 (143.325169ms)
✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (518.142029ms) ✔ T3: a seat thread renamed to another seat exits 1 naming thread, title and roles (423.944271ms)
✔ T3: an unmapped thread addressed as a seat exits 1 (569.946942ms) ✔ T3: an unmapped thread addressed as a seat exits 1 (405.235245ms)
✔ T3: a header to another thread id is not cross-checked (787.859966ms) ✔ T3: a header to another thread id is not cross-checked (399.242708ms)
✔ T3: no project, or two, for this root exits 1 (1458.185096ms) ✔ T3: no project, or two, for this root exits 1 (983.743767ms)
✔ T3: a removed column exits 1 and names it (374.658545ms) ✔ T3: a removed column exits 1 and names it (243.999694ms)
✔ T3: a missing table exits 1 and names it (333.135189ms) ✔ T3: a missing table exits 1 and names it (242.680597ms)
✔ T3: a counted row with an unknown role exits 1 without its text (518.1692ms) ✔ T3: a counted row with an unknown role exits 1 without its text (381.381698ms)
✔ T3: a counted row with non-text content exits 1 without its text (470.069153ms) ✔ T3: a counted row with non-text content exits 1 without its text (409.893147ms)
✔ T3: a counted row with an unparseable created_at exits 1 without its text (594.355264ms) ✔ T3: a counted row with an unparseable created_at exits 1 without its text (436.160156ms)
✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (1101.049509ms) ✔ T3: a missing orchestration_events makes the diagnostic unknown and keeps the counts (777.585819ms)
✔ T3: a human message with no event counts in humanWithoutEvent (559.271689ms) ✔ T3: a human message with no event counts in humanWithoutEvent (411.360534ms)
✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (909.949273ms) ✔ T3: an unparseable event makes the diagnostic unknown and keeps the counts (723.421105ms)
✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (971.023265ms) ✔ T3: an event with no string messageId makes the diagnostic unknown and keeps the counts (715.375859ms)
✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (424.377883ms) ✔ T3: an error that is not from SQLite is rethrown, not reported as a database failure (397.517748ms)
✔ T3: a symlink at ~/.t3 exits 1 (178.873006ms) ✔ T3: a symlink at ~/.t3 exits 1 (158.225544ms)
✔ T3: a symlink at ~/.t3/userdata exits 1 (194.873019ms) ✔ T3: a symlink at ~/.t3/userdata exits 1 (165.345952ms)
✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (138.790741ms) ✔ T3: a symlink at ~/.t3/userdata/state.sqlite exits 1 (140.074401ms)
✔ T3: with --t3-db, a symlinked file or directory exits 1 (598.260244ms) ✔ T3: with --t3-db, a symlinked file or directory exits 1 (487.275139ms)
✔ T3 WAL: the newest message only in -wal, writer attached, is counted (616.999343ms) ✔ T3 WAL: the newest message only in -wal, writer attached, is counted (399.702885ms)
✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (747.300765ms) ✔ T3 WAL: stopped cleanly, counts are correct and the main file is unchanged (393.441586ms)
✔ T3 WAL: -wal without -shm in a writable directory is read (669.972464ms) ✔ T3 WAL: -wal without -shm in a writable directory is read (499.066966ms)
✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (859.988644ms) ✔ T3 WAL: -wal without -shm in a read-only directory exits 1 (453.861577ms)
✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (780.564496ms) ✔ T3 WAL: stopped cleanly in a read-only directory exits 1 (383.574487ms)
✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5802.477411ms) ✔ T3: a lock held past the 5 s busy timeout exits 1 and names --no-t3 (5389.251886ms)
✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.814873ms) ✔ a done row whose closing issue is open is a violation; a row that is not done is not (1.769997ms)
✔ an issue several rows close is expected closed only once all of them are done (0.493759ms) ✔ an issue several rows close is expected closed only once all of them are done (0.479396ms)
✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.366812ms) ✔ closure needs positive evidence: unknown is undecided, and so is a skipped or short issue check (0.352468ms)
✔ each owner of an in-progress or in-review row gets one liveness class (7.558511ms) ✔ each owner of an in-progress or in-review row gets one liveness class (7.450645ms)
✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.382616ms) ✔ a required row not done after 14 days is a violation; a legacy row uses genesis as its lower bound (0.375297ms)
✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.239616ms) ✔ an ISO requiredSince, as `set required` writes it, ages from its UTC day; one that does not parse is a violation (0.202726ms)
✔ the text section always ends in a count and a result, and never prints a full pass (0.322972ms) ✔ the text section always ends in a count and a result, and never prints a full pass (0.31282ms)
✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (24.300433ms) ✔ pidAlive: a running pid is present, an exited one is gone, and EPERM still means present (23.634577ms)
✔ issue states: open list first, then the metric page, then at most 10 lookups (299.016341ms) ✔ issue states: open list first, then the metric page, then at most 10 lookups (278.392933ms)
✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (379.027884ms) ✔ a full open list: lookups settle what it leaves out, and only an unsettled issue keeps it undecided (377.736803ms)
✔ the open list refuses on a failed call or a bad record, and never echoes the helper (187.181352ms) ✔ the open list refuses on a failed call or a bad record, and never echoes the helper (169.378535ms)
✔ a helper call past the deadline is killed with its child, and the call reports it (2008.003463ms) ✔ a helper call past the deadline is killed with its child, and the call reports it (2007.711363ms)
✔ readQueue loads queue.json through the queue validator and refuses anything else (242.189312ms) ✔ readQueue loads queue.json through the queue validator and refuses anything else (119.664719ms)
✔ protected changes list every in-range entry that changes a required or parked row (525.618188ms) ✔ protected changes list every in-range entry that changes a required or parked row (444.485151ms)
✔ the CLI prints the queue section above the weekly table and under a queue key in --json (563.216329ms) ✔ the CLI prints the queue section above the weekly table and under a queue key in --json (526.301912ms)
✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (284.538284ms) ✔ a queue with nothing wrong prints 0 violations and a reduced pass, never a full pass (251.548181ms)
✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (343.338245ms) ✔ --no-issues makes no call and leaves the issue checks undecided; --no-queue skips the section (320.738002ms)
✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (308.846971ms) ✔ the CLI refuses a bad queue before any call, and a failed open list with exit 2 (288.417953ms)
✔ --unsupported-runtime repeats once per seat and takes a seat name (342.898875ms) ✔ --unsupported-runtime repeats once per seat and takes a seat name (346.556078ms)
✔ an unreadable config makes every owner invalid instead of passing them (197.221174ms) ✔ an unreadable config makes every owner invalid instead of passing them (179.623698ms)
ℹ tests 78 ℹ tests 78
ℹ suites 0 ℹ suites 0
ℹ pass 78 ℹ pass 78
@@ -83,4 +83,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 26163.120468 ℹ duration_ms 19490.132391
+70 -70
View File
@@ -1,72 +1,72 @@
✔ pure resolution selects current default or explicit enrolled account (2.054528ms) ✔ pure resolution selects current default or explicit enrolled account (2.429019ms)
✔ scope is explicit, bounded and never inferred (1.596542ms) ✔ scope is explicit, bounded and never inferred (2.008188ms)
✔ fork pin is preserved against default change, override, missing account and revocation (0.79176ms) ✔ fork pin is preserved against default change, override, missing account and revocation (1.028803ms)
✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (1.159723ms) ✔ unenrolled account/provider, missing harness, model expansion and native model ceiling refuse (0.937079ms)
✔ only explicit synthetic credential forms and internal fixture stores admitted (6.518592ms) ✔ only explicit synthetic credential forms and internal fixture stores admitted (6.706158ms)
✔ two concurrent workspaces of the same agent publish distinct complete private generations (68.351516ms) ✔ two concurrent workspaces of the same agent publish distinct complete private generations (66.687633ms)
✔ same execution ID is exclusively claimed and cannot overwrite a published generation (33.972978ms) ✔ same execution ID is exclusively claimed and cannot overwrite a published generation (35.350014ms)
✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (52.451468ms) ✔ failed generation after-auth preserves prior files, records failure and refuses blind same-ID retry (53.622341ms)
✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (58.496772ms) ✔ failed generation before-publish preserves prior files, records failure and refuses blind same-ID retry (62.771761ms)
✔ credential lock contention refuses without duplicate side effects (18.327397ms) ✔ credential lock contention refuses without duplicate side effects (14.189607ms)
✔ symlinked pre-existing final target is refused and never followed (44.660615ms) ✔ symlinked pre-existing final target is refused and never followed (27.916602ms)
✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.259178ms) ✔ invalid registry cannot resolve; no fallback to supplied partial entries (0.324141ms)
✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (35.197219ms) ✔ post-publication failure records uncertainty, preserves complete generation and prevents replay (27.397863ms)
✔ expired credentials refresh under transaction and subsequent generation reuses rotation (120.496699ms) ✔ expired credentials refresh under transaction and subsequent generation reuses rotation (95.071147ms)
✔ refresh failure retains prior generation and store state (98.926492ms) ✔ refresh failure retains prior generation and store state (66.210873ms)
✔ refresh timeout retains prior generation and store state (173.954281ms) ✔ refresh timeout retains prior generation and store state (145.945902ms)
✔ refresh malformed retains prior generation and store state (94.802167ms) ✔ refresh malformed retains prior generation and store state (67.020929ms)
✔ concurrent refresh on same account refuses contention while unrelated account proceeds (216.827049ms) ✔ concurrent refresh on same account refuses contention while unrelated account proceeds (215.216001ms)
✔ invalid refresh options refuse before burning claim (50.160412ms) ✔ invalid refresh options refuse before burning claim (32.635753ms)
✔ fixed fake process rotates both OAuth fields without mutating caller input (31.053588ms) ✔ fixed fake process rotates both OAuth fields without mutating caller input (34.494178ms)
✔ concurrent isolated processes preserve separate provider credentials (27.535508ms) ✔ concurrent isolated processes preserve separate provider credentials (30.51094ms)
✔ fake failure is refused with fixed diagnostics (22.739666ms) ✔ fake failure is refused with fixed diagnostics (28.545153ms)
✔ fake malformed is refused with fixed diagnostics (27.208676ms) ✔ fake malformed is refused with fixed diagnostics (23.610575ms)
✔ fake timeout is refused with fixed diagnostics (103.025381ms) ✔ fake timeout is refused with fixed diagnostics (105.288146ms)
✔ fake unchanged is refused with fixed diagnostics (25.003792ms) ✔ fake unchanged is refused with fixed diagnostics (33.437171ms)
✔ caller executable/environment injection is rejected before spawning (0.310957ms) ✔ caller executable/environment injection is rejected before spawning (0.361978ms)
✔ valid fixture tree validates and lists without secrets (72.979155ms) ✔ valid fixture tree validates and lists without secrets (78.943192ms)
✔ unknown-field refuses (0.36807ms) ✔ unknown-field refuses (0.466063ms)
✔ invalid-id refuses uppercase and traversal shapes (0.267451ms) ✔ invalid-id refuses uppercase and traversal shapes (0.780417ms)
✔ plain-http baseUrl requires allowInsecureTransport (0.341539ms) ✔ plain-http baseUrl requires allowInsecureTransport (0.329672ms)
✔ native provider rejects allowInsecureTransport (0.128697ms) ✔ native provider rejects allowInsecureTransport (0.14812ms)
✔ unsupported credential type and kind refuse (0.153047ms) ✔ unsupported credential type and kind refuse (0.179903ms)
✔ account provider-path mismatch refuses (0.108869ms) ✔ account provider-path mismatch refuses (0.137888ms)
✔ profile account refs must be provider/account shaped (0.223655ms) ✔ profile account refs must be provider/account shaped (0.278809ms)
✔ seat selection accepts fork pin field, validates account refs (0.67099ms) ✔ seat selection accepts fork pin field, validates account refs (0.228679ms)
✔ harness manifest id must equal executable (gate 1) (0.208191ms) ✔ harness manifest id must equal executable (gate 1) (0.237291ms)
✔ CLI validate: duplicate provider id across files refuses (30.331947ms) ✔ CLI validate: duplicate provider id across files refuses (34.642672ms)
✔ CLI validate: missing referenced provider/account refuse (35.752038ms) ✔ CLI validate: missing referenced provider/account refuse (37.622294ms)
✔ CLI validate: broken JSON refuses without secret echo (26.482716ms) ✔ CLI validate: broken JSON refuses without secret echo (38.871585ms)
✔ CLI usage errors exit 2 (52.783092ms) ✔ CLI usage errors exit 2 (64.004687ms)
✔ credential.json sibling presence does not break validation and is never read (67.16895ms) ✔ credential.json sibling presence does not break validation and is never read (74.131397ms)
✔ D1 missing, empty and structurally empty roots refuse, no list projection (177.535483ms) ✔ D1 missing, empty and structurally empty roots refuse, no list projection (198.454483ms)
✔ D1 required directory auth cannot be absent (53.462767ms) ✔ D1 required directory auth cannot be absent (67.557644ms)
✔ D1 required directory auth/providers cannot be absent (58.208941ms) ✔ D1 required directory auth/providers cannot be absent (62.914613ms)
✔ D1 required directory auth/accounts cannot be absent (60.123059ms) ✔ D1 required directory auth/accounts cannot be absent (66.596975ms)
✔ D1 required directory auth/settings cannot be absent (64.777835ms) ✔ D1 required directory auth/settings cannot be absent (62.26027ms)
✔ D1 required directory harnesses cannot be absent (63.810214ms) ✔ D1 required directory harnesses cannot be absent (62.855613ms)
✔ D1 root file and unreadable metadata refuse (117.813395ms) ✔ D1 root file and unreadable metadata refuse (110.859654ms)
✔ D2 no symlink traversal at auth/providers/openai-codex.json (54.008571ms) ✔ D2 no symlink traversal at auth/providers/openai-codex.json (56.377228ms)
✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (65.397095ms) ✔ D2 no symlink traversal at auth/accounts/openai-codex/homelab-openai (61.738757ms)
✔ D2 no symlink traversal at auth/providers (54.760645ms) ✔ D2 no symlink traversal at auth/providers (58.632499ms)
✔ D2 no symlink traversal at auth (56.803918ms) ✔ D2 no symlink traversal at auth (61.411345ms)
✔ D2 root and ancestor symlinks and lexical traversal refuse (165.243458ms) ✔ D2 root and ancestor symlinks and lexical traversal refuse (162.750712ms)
✔ private filesystem modes enforced for root (57.733162ms) ✔ private filesystem modes enforced for root (51.93814ms)
✔ private filesystem modes enforced for auth (53.800829ms) ✔ private filesystem modes enforced for auth (53.662983ms)
✔ private filesystem modes enforced for auth/providers/openai-codex.json (64.945974ms) ✔ private filesystem modes enforced for auth/providers/openai-codex.json (58.640811ms)
✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (63.277721ms) ✔ private filesystem modes enforced for auth/accounts/openai-codex/homelab-openai/account.json (63.272857ms)
✔ D3 numeric version 1 only across all record kinds (1.246662ms) ✔ D3 numeric version 1 only across all record kinds (1.40059ms)
✔ D4 nested unknown keys and missing per-kind required fields refuse (64.557054ms) ✔ D4 nested unknown keys and missing per-kind required fields refuse (64.66648ms)
✔ D5 unenrolled default refuses even when account exists (65.244555ms) ✔ D5 unenrolled default refuses even when account exists (65.199118ms)
✔ D6 provider/account credential type must match (69.207623ms) ✔ D6 provider/account credential type must match (66.85376ms)
✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.51095ms) ✔ D7 every harness endpoint enforces HTTP opt-in and shape (0.478063ms)
✔ D8 URLs reject embedded credentials and unsupported protocols without echo (166.65518ms) ✔ D8 URLs reject embedded credentials and unsupported protocols without echo (174.054132ms)
✔ D9 malformed JSON diagnostics contain no content excerpt (62.800794ms) ✔ D9 malformed JSON diagnostics contain no content excerpt (60.943055ms)
✔ D10 missing metadata is missing-path, not invalid-json (56.760167ms) ✔ D10 missing metadata is missing-path, not invalid-json (64.69376ms)
✔ D10 library returns no partial entries on any invalid record (74.177067ms) ✔ D10 library returns no partial entries on any invalid record (71.901694ms)
✔ null/scalar/array metadata refuses without stack or echo (229.571906ms) ✔ null/scalar/array metadata refuses without stack or echo (223.397559ms)
✔ credential sibling is never opened, even when an unreadable symlink (32.269336ms) ✔ credential sibling is never opened, even when an unreadable symlink (31.932356ms)
✔ oversized metadata refuses before parsing (54.703633ms) ✔ oversized metadata refuses before parsing (55.12523ms)
ℹ tests 69 ℹ tests 69
ℹ suites 0 ℹ suites 0
ℹ pass 69 ℹ pass 69
@@ -74,4 +74,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 2162.523966 ℹ duration_ms 2194.660704
+149 -149
View File
@@ -1,153 +1,153 @@
✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1260.532537ms) ✔ F1: an ordinary commit after update-ref is refused until step 8; then it commits and the queue stays at C (1279.93069ms)
✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1076.47454ms) ✔ F1: a plain `commit -e` whose guard ran before update-ref fails at its own HEAD update (1061.181758ms)
ℹ git commit -e: index.lock free during the editor ℹ 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 (1085.134721ms) ✔ F1: a `commit -e -- path` whose guard ran before update-ref fails at its own HEAD update (1039.890109ms)
ℹ git commit -e -- src.txt: index.lock held during the editor ℹ 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 (1061.824905ms) ✔ F1: step 8 with index.lock held exits 3, and ordinary commits stay refused until the printed command runs (1113.030413ms)
✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1066.708895ms) ✔ F1: HEAD moving after the step-7 guard check and before update-ref: refused, nothing published (1049.393326ms)
✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1017.127371ms) ✔ F1: H is recorded before the canary, so HEAD moving during the step-1 canary is refused at step 7 (1110.939364ms)
✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1355.445314ms) ✔ F1: a queue commit landing after H is recorded: step 1 says HEAD moved, not the guard (1901.512818ms)
✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1001.736236ms) ✔ F1: a queue commit landing between the HEAD check and the canary: the failed clean run is reported as HEAD moved (1153.947616ms)
✔ F1: a shared-index change during the procedure is not committed (1123.875559ms) ✔ F1: a shared-index change during the procedure is not committed (1320.421343ms)
✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1133.958432ms) ✔ F1: a queue path staged after update-ref: step 8 stops and touches nothing (1268.461107ms)
✔ F1: a missing or a different hook refuses (787.253511ms) ✔ F1: a missing or a different hook refuses (887.119522ms)
✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1268.622115ms) ✔ F1: same bytes without the exec bit, a symlinked hook, and core.hooksPath in the local or global scope each refuse before update-ref (1479.523604ms)
✔ F1: the canary refuses a hook that git would not run (767.910797ms) ✔ F1: the canary refuses a hook that git would not run (753.192489ms)
✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1000.642982ms) ✔ F1: the guard deactivated after step 1 is refused at the step-7 recheck (1057.980151ms)
✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (1216.284271ms) ✔ bootstrap: implementation-only HEAD, the guard, genesis, the --genesis commit, then an extending commit (1014.061543ms)
✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (1072.93868ms) ✔ bootstrap: --genesis with a base present, no base without --genesis, an op before the first commit, a changed map, another branch (1062.788887ms)
✔ bootstrap: the archived tests and validator run outside any repository (1114.228462ms) ✔ bootstrap: the archived tests and validator run outside any repository (999.382289ms)
✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (1133.802007ms) ✔ general: an unrelated staged file stays staged, and the committed blobs are the snapshot bytes (1034.48839ms)
✔ general: a queue write after the snapshot is not committed (1588.451551ms) ✔ general: a queue write after the snapshot is not committed (1544.53368ms)
✔ general: a snapshot whose log does not extend the base refuses (1345.572094ms) ✔ general: a snapshot whose log does not extend the base refuses (1059.429735ms)
✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (158.499452ms) ✔ general: install-hook privilege, repair of a same-bytes hook, and its refusals (173.000296ms)
✔ general: environment overrides, a linked worktree and usage (809.991692ms) ✔ general: environment overrides, a linked worktree and usage (677.48337ms)
✔ general: a queue path staged before the run refuses at step 1 (752.034412ms) ✔ general: a queue path staged before the run refuses at step 1 (712.677427ms)
✔ general: HEAD's queue tests failing in the archive refuse (974.440619ms) ✔ general: HEAD's queue tests failing in the archive refuse (950.893886ms)
✔ genesis document serializes deterministically and replays (4.243965ms) ✔ genesis document serializes deterministically and replays (3.684709ms)
✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.363785ms) ✔ a hand edit that stays valid JSON fails replay; a formatting-only edit fails re-serialization (2.123963ms)
✔ a tampered result, receipt or viewSha fails replay (2.23881ms) ✔ a tampered result, receipt or viewSha fails replay (2.124902ms)
✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.180499ms) ✔ op ids: 8 to 72 characters for callers, 80 in the log for .outcome entries (0.160084ms)
✔ add: defaults for an ordinary seat, privileged extras, refusals (4.59724ms) ✔ add: defaults for an ordinary seat, privileged extras, refusals (3.307489ms)
✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (5.711521ms) ✔ matrix: queued→briefed privileged; briefed→in-progress owner with after satisfied (6.854022ms)
✔ matrix: release, review round, changes requested and waiting-on-jason (12.703351ms) ✔ matrix: release, review round, changes requested and waiting-on-jason (11.855146ms)
✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (7.854129ms) ✔ matrix J5: in-review→done by the gate owner with evidence naming the current round (9.291068ms)
✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (18.361508ms) ✔ review issue, lead decision 23: none refuses, one is used, several need --issue, later rounds keep it (18.606488ms)
✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.443285ms) ✔ the row schema refuses a round with a null issue, and the A1 review shape (P2) (2.376603ms)
✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1321.934974ms) ✔ matrix R1: every state × target × actor class matches 8.7, gate owner jason or not, required or not (1351.108857ms)
✔ matrix: blocked keeps the claim and returns only to previousState (3.498242ms) ✔ matrix: blocked keeps the claim and returns only to previousState (3.790996ms)
✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.258188ms) ✔ matrix J4: parking is Jason's, refused while required; unpark returns to queued (2.439068ms)
✔ field edits: who may change what (4.444263ms) ✔ field edits: who may change what (5.543327ms)
✔ set issues keeps a logged narrowing of closes (N10) (2.396823ms) ✔ set issues keeps a logged narrowing of closes (N10) (2.904337ms)
✔ text the table shows refuses \ and <, everywhere it enters (N8) (1.627096ms) ✔ text the table shows refuses \ and <, everywhere it enters (N8) (2.09567ms)
✔ every accepted text renders to nine cells on every row (N8) (20.232327ms) ✔ every accepted text renders to nine cells on every row (N8) (25.296747ms)
✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.463181ms) ✔ genesis: the map refuses an owner among its row's reviewers; replay doesn't (2026-10-04) (0.59648ms)
✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.396766ms) ✔ times and dates must be calendar values, not just the shape (2026-10-04) (1.736801ms)
✔ replay holds every op id to the caller's rule (N11) (4.32996ms) ✔ replay holds every op id to the caller's rule (N11) (5.412384ms)
✔ note: owner, listed reviewer or privileged; empty clears (1.31496ms) ✔ note: owner, listed reviewer or privileged; empty clears (1.149404ms)
✔ assign moves the claim with the owner; done clears it (2.206523ms) ✔ assign moves the claim with the owner; done clears it (2.661687ms)
✔ render is byte-stable and escapes pipes (0.421495ms) ✔ render is byte-stable and escapes pipes (0.574402ms)
✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.547949ms) ✔ view classification: current, genuine stale, edited stale marker, changed current body, markers (0.646752ms)
✔ next: resume, then review, then start, then wait, then nothing; lowest id first (15.614361ms) ✔ next: resume, then review, then start, then wait, then nothing; lowest id first (16.872894ms)
✔ canonical args make a retry's identity independent of list order (0.261014ms) ✔ canonical args make a retry's identity independent of list order (0.28851ms)
✔ manifests, headings and blob ids (0.41674ms) ✔ manifests, headings and blob ids (0.390992ms)
✔ the migration map: one queue-map block, exact keys (0.712422ms) ✔ the migration map: one queue-map block, exact keys (0.504008ms)
✔ every call but `queue` reaches the seat CLI exactly as before A2 (593.551394ms) ✔ every call but `queue` reaches the seat CLI exactly as before A2 (594.850867ms)
✔ `queue` reaches the queue CLI with the rest of the arguments (214.598714ms) ✔ `queue` reaches the queue CLI with the rest of the arguments (200.393007ms)
✔ the pre-A2 fixture is the script A2 changed (0.270252ms) ✔ the pre-A2 fixture is the script A2 changed (0.278964ms)
✔ acquire publishes the record by link; release removes only its own lock (13.699806ms) ✔ acquire publishes the record by link; release removes only its own lock (16.628947ms)
✔ a kill between the temp write and the link leaves no lock (54.469267ms) ✔ a kill between the temp write and the link leaves no lock (52.152311ms)
✔ a short or failed temp write refuses and leaves no lock and no temp (9.326024ms) ✔ a short or failed temp write refuses and leaves no lock and no temp (9.972978ms)
✔ a link error other than EEXIST refuses (6.479184ms) ✔ a link error other than EEXIST refuses (6.223349ms)
✔ an error after the link releases the lock: unreadable gate, failing temp stat (17.151141ms) ✔ an error after the link releases the lock: unreadable gate, failing temp stat (17.749792ms)
✔ a release that fails on a gate path is reported, never a stack trace (P1) (42.844766ms) ✔ a release that fails on a gate path is reported, never a stack trace (P1) (38.678105ms)
✔ a paused holder: another writer waits 10 s, then refuses naming it live (10092.670947ms) ✔ a paused holder: another writer waits 10 s, then refuses naming it live (10092.400082ms)
✔ two concurrent unlockers: the second refuses on the gate (44.779225ms) ✔ two concurrent unlockers: the second refuses on the gate (48.091667ms)
✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (22.456092ms) ✔ a writer publishing during an unlock, lock first: unlock sees it live and refuses (25.144331ms)
✔ a writer publishing during an unlock, gate first: the writer releases and refuses (23.746529ms) ✔ a writer publishing during an unlock, gate first: the writer releases and refuses (21.298481ms)
✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (26.303537ms) ✔ a gate swapped while held is left in place and reported, on success and on refusal (N1) (11.879281ms)
✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (18.32773ms) ✔ a reused pid within one boot is mismatch; unlock removes the lock and never signals the process (21.635177ms)
✔ the same pid and start on a different boot is mismatch (0.773792ms) ✔ the same pid and start on a different boot is mismatch (0.427637ms)
✔ a foreign host is unknown whatever the local pid says; unlock refuses (82.512363ms) ✔ a foreign host is unknown whatever the local pid says; unlock refuses (78.81943ms)
✔ unreadable /proc: classification is unknown and acquire refuses (0.801342ms) ✔ unreadable /proc: classification is unknown and acquire refuses (0.782644ms)
✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.247735ms) ✔ invalid records: empty, unparsable, wrong keys, bad start or boot (0.192916ms)
✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (24.859261ms) ✔ a stale gate blocks writers; --check-gate says mismatch for a reused pid (23.436998ms)
✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (28.793256ms) ✔ a delayed release by a dead owner, after unlock and a new owner: the inode check keeps the new lock (34.209647ms)
✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (12.386533ms) ✔ release checks the inode too: a byte-identical lock file with a new inode is left in place (11.896035ms)
✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (53.426691ms) ✔ unlock refuses a live, unknown or invalid lock, and does nothing without one (45.435129ms)
✔ the migration map validates and renders the golden genesis table (3.605974ms) ✔ the migration map validates and renders the golden genesis table (3.068396ms)
✔ the marked QUEUE.md holds every row and parked item between its markers (1.374021ms) ✔ the marked QUEUE.md holds every row and parked item between its markers (1.179375ms)
✔ map-check reports each kind of drift (3.769497ms) ✔ map-check reports each kind of drift (3.696429ms)
✔ a request posts once as the requester; a retry sends nothing (925.490028ms) ✔ a request posts once as the requester; a retry sends nothing (793.821048ms)
✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (4633.824139ms) ✔ each transport answer maps to posted, failed or uncertain (8.9 step 3) (4509.211371ms)
✔ the pre-send checks: GET user must name the requester, under the deadline (1649.705611ms) ✔ the pre-send checks: GET user must name the requester, under the deadline (1671.143785ms)
✔ the lead's request refuses a token for login sage (1002.199905ms) ✔ the lead's request refuses a token for login sage (1164.929541ms)
✔ the credential file: the seat's own, 0600, no symlink, never the shared default (936.047983ms) ✔ the credential file: the seat's own, 0600, no symlink, never the shared default (925.760743ms)
✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (2754.343636ms) ✔ an unresolved request blocks a new request, a new round, waiting-on-jason and done (2618.360505ms)
✔ a same-op retry after a kill sends nothing, even with a stale view (3581.147531ms) ✔ a same-op retry after a kill sends nothing, even with a stale view (3532.557044ms)
✔ a held lock at the outcome exits 3 and names what the transport said (972.520861ms) ✔ a held lock at the outcome exits 3 and names what the transport said (774.476475ms)
✔ late outcomes: after an abandon, and after a resolve with the same or another id (3471.630995ms) ✔ late outcomes: after an abandon, and after a resolve with the same or another id (2464.206174ms)
✔ resolve checks the comment: issue, markers, round, candidate and author (2199.757148ms) ✔ resolve checks the comment: issue, markers, round, candidate and author (1885.971503ms)
✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (963.207915ms) ✔ the lead resolves a seat's request: the comment's author is the requester, fetched with the lead's token (825.961039ms)
✔ validateRow checks a request round's shape, which every replayed entry must keep (871.059553ms) ✔ validateRow checks a request round's shape, which every replayed entry must keep (562.251089ms)
✔ request, changes, a new candidate, approval: every round pinned; no review files (2129.60435ms) ✔ request, changes, a new candidate, approval: every round pinned; no review files (1806.225057ms)
✔ a row with no reviewers opens a round that posts nothing (1824.373429ms) ✔ a row with no reviewers opens a round that posts nothing (1256.137801ms)
✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1550.689384ms) ✔ verify-commit: a prospective tree must hold exactly the candidate's paths (1438.083058ms)
✔ semantics: v1 entries replay as before; review entries need v2 (619.323764ms) ✔ semantics: v1 entries replay as before; review entries need v2 (498.822158ms)
✔ set reviewers refuses the row's owner (2026-09-28) (427.726772ms) ✔ set reviewers refuses the row's owner (2026-09-28) (357.35333ms)
✔ the owner records no verdict, even as a listed reviewer (760.230018ms) ✔ the owner records no verdict, even as a listed reviewer (501.631343ms)
✔ a request comment over the length limit is not sent (640.357309ms) ✔ a request comment over the length limit is not sent (549.087325ms)
✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (775.036707ms) ✔ a late POST on a closed row leaves a conflict nothing can resolve, and resolve asks nothing (620.99129ms)
✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2832.128294ms) ✔ a Jason-gated row reaches waiting-on-jason only on every reviewer's approval (2450.332075ms)
✔ genesis: refusals before anything is written (403.047086ms) ✔ genesis: refusals before anything is written (422.824988ms)
✔ genesis: the map must be committed, well formed, with committed briefs and seats (623.222448ms) ✔ genesis: the map must be committed, well formed, with committed briefs and seats (612.601687ms)
✔ genesis: markers, a stray witness, once only; a retry returns the receipt (535.769096ms) ✔ genesis: markers, a stray witness, once only; a retry returns the receipt (584.757227ms)
✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (411.50267ms) ✔ genesis: a file holding genesis alone with no witness is confirmed by sync or a retry (396.183357ms)
✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (654.999389ms) ✔ canonical checks: worktree, second clone, detached HEAD, wrong branch, GIT_DIR, foreign code; a symlink works (637.054022ms)
✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (863.898734ms) ✔ --by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more (N12) (832.113715ms)
✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (732.001521ms) ✔ op ids: missing, too long, reserved; a retry answers; another payload refuses (742.015941ms)
✔ a retried add returns the id it first allocated, after reassignment and after done (961.703142ms) ✔ a retried add returns the id it first allocated, after reassignment and after done (925.841231ms)
✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (767.524552ms) ✔ Rocko's S4 schedule: a lost result, another writer, then the retry opens no second round (686.636896ms)
✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1288.336113ms) ✔ the review issue and the evidence round through the CLI (lead decision 23, 8.7) (1482.273824ms)
✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (851.663689ms) ✔ claims and add defaults through the CLI; candidates are manifests or reachable commits (1023.354216ms)
✔ add, set reviewers and assign refuse the row's owner as a reviewer (568.745548ms) ✔ add, set reviewers and assign refuse the row's owner as a reviewer (628.965565ms)
✔ the working-brief check: a changed working copy refuses the start and flags next (688.532933ms) ✔ the working-brief check: a changed working copy refuses the start and flags next (782.135781ms)
✔ next: resume first, then nothing for an idle seat; needs a seat (343.272758ms) ✔ next: resume first, then nothing for an idle seat; needs a seat (399.491743ms)
✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (736.097429ms) ✔ view stale: new ops and verify refuse naming the unshown op; retries answer; reads warn; render fixes (855.215994ms)
✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1284.828802ms) ✔ view unknown: a hand edit, an old marker over an edited body, missing or duplicate markers (1419.455796ms)
✔ a hand edit to queue.json refuses every verb, reads included (686.215095ms) ✔ a hand edit to queue.json refuses every verb, reads included (725.348502ms)
✔ verify and render --check leave bytes and mtimes unchanged (452.027098ms) ✔ verify and render --check leave bytes and mtimes unchanged (508.55243ms)
✔ render is byte-stable across runs and repositories (323.191895ms) ✔ render is byte-stable across runs and repositories (331.023232ms)
✔ snapshot and verify --snapshot (831.73524ms) ✔ snapshot and verify --snapshot (904.593087ms)
✔ usage errors exit 4 (533.103717ms) ✔ usage errors exit 4 (614.772576ms)
✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (343.935558ms) ✔ a short write, ENOSPC or a file fsync failure: nothing visible, temp removed (299.095639ms)
✔ a rename failure: nothing visible, temp removed (209.136069ms) ✔ a rename failure: nothing visible, temp removed (163.482344ms)
✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (259.837306ms) ✔ a directory fsync failure: uncertain, exit 3, no receipt; the tail refuses new ops; a retry confirms (236.68444ms)
✔ a directory fsync failure, then sync names the op (406.195753ms) ✔ a directory fsync failure, then sync names the op (405.326115ms)
✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (220.710753ms) ✔ a witness write failure: uncertain, durable, exit 3; the view is untouched (237.912353ms)
✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (205.853729ms) ✔ the .git fsync after the witness rename fails: uncertain, exit 3, the witness says so (200.117919ms)
✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (252.052248ms) ✔ confirming a tail fsyncs queue.json and docs/plans before the witness; either failure changes nothing (203.284657ms)
✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (201.571019ms) ✔ the docs/plans fsync after the view rename fails: the op stands, the view is written, a warning says so (204.114537ms)
✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (228.011398ms) ✔ a lock swapped while held is left in place and reported, on a receipt and on a refusal (231.80633ms)
✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (234.160899ms) ✔ a lock that cannot be released after an op is reported, on a receipt and on a refusal (216.257237ms)
✔ unlock prints a swapped gate's warning on stderr, the result on stdout (145.80488ms) ✔ unlock prints a swapped gate's warning on stderr, the result on stdout (161.411402ms)
✔ a view write that fails keeps the op and reports a stale view (211.930761ms) ✔ a view write that fails keeps the op and reports a stale view (189.979668ms)
✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (673.688149ms) ✔ SIGKILL before the rename: nothing recorded; the retry removes the leftover temp (674.312804ms)
✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (686.847329ms) ✔ SIGKILL after the rename, before the witness: the tail refuses new ops and sync names the op (692.940046ms)
✔ SIGKILL after the witness, before the view: the stale refusal names the op (650.580533ms) ✔ SIGKILL after the witness, before the view: the stale refusal names the op (634.848768ms)
✔ SIGKILL after the view, before the receipt: the retry returns the receipt (704.871034ms) ✔ SIGKILL after the view, before the receipt: the retry returns the receipt (678.631233ms)
✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (262.774005ms) ✔ git checkout between steps 1 and 7: step 7 refuses and nothing is written (258.58497ms)
✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1074.729772ms) ✔ git stash restoring an older valid pair: history lost; accept-history needs privilege, a reason and --yes (1173.652358ms)
✔ a deleted witness: refused after the locked recheck; accept-history records it absent (386.684099ms) ✔ a deleted witness: refused after the locked recheck; accept-history records it absent (433.525482ms)
✔ a header edit during a write: the op stands, the view write is skipped with a warning (227.042343ms) ✔ a header edit during a write: the op stands, the view write is skipped with a warning (277.216377ms)
✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (227.289466ms) ✔ a reader paused between the witness and the file while a writer finishes: no lost-history report (245.896099ms)
✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (230.099313ms) ✔ file-then-witness order forced by a hook: the locked recheck prevents a false report (263.774246ms)
✔ a writer paused before and after the witness rename: readers see a tail, then a match (226.156708ms) ✔ a writer paused before and after the witness rename: readers see a tail, then a match (262.407703ms)
✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (621.321999ms) ✔ a true rollback is reported only after the locked recheck; a held lock names its holder instead (646.952244ms)
✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (649.492684ms) ✔ an accept-history in progress: an unlocked reader waits on the lock and never reports lost history (617.828779ms)
✔ the platform check refuses other filesystems (156.699881ms) ✔ the platform check refuses other filesystems (187.535157ms)
✔ tmpfs passes only a test layer that allows it (N5) (259.564709ms) ✔ tmpfs passes only a test layer that allows it (N5) (243.359408ms)
✔ unlock keeps a multi-line lock record on stdout (P3) (228.264417ms) ✔ unlock keeps a multi-line lock record on stdout (P3) (252.39877ms)
ℹ tests 148 ℹ tests 148
ℹ suites 0 ℹ suites 0
ℹ pass 148 ℹ pass 148
@@ -155,4 +155,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 35595.254131 ℹ duration_ms 31286.166236
+28 -28
View File
@@ -1,30 +1,30 @@
✔ resolveSeat: by name under --repo resolves the repo layout (2.38375ms) ✔ resolveSeat: by name under --repo resolves the repo layout (1.330962ms)
✔ resolveSeat: by path resolves the fleet layout (0.307ms) ✔ resolveSeat: by path resolves the fleet layout (0.335526ms)
✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.687428ms) ✔ resolveSeat: refusals for missing dir, missing launch.sh, non-executable launch.sh, invalid name, and unknown layout (0.743892ms)
✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.671855ms) ✔ tmuxContext: outside tmux, default socket, custom socket, and exec failure (0.649939ms)
✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.816835ms) ✔ makeRegistration produces a record that validates; each shape violation throws SeatError (0.799606ms)
✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (1.280225ms) ✔ writeRegistration/readRegistration: round trip, permissions, absence, and malformed records (0.834488ms)
✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.155249ms) ✔ updateTask: changes task, taskSetBy and updatedAt only, and refuses appropriately (1.132624ms)
✔ CLI launch: registers, execs the fake launch script, and passes args through (27.419109ms) ✔ CLI launch: registers, execs the fake launch script, and passes args through (27.473766ms)
✔ CLI launch: --harness lands in the record (28.863116ms) ✔ CLI launch: --harness lands in the record (27.131259ms)
✔ CLI launch: the launch script's own exit code passes through (27.969508ms) ✔ CLI launch: the launch script's own exit code passes through (29.937184ms)
✔ CLI launch: relaunching a seat rewrites the one registration record (56.792485ms) ✔ CLI launch: relaunching a seat rewrites the one registration record (58.389233ms)
✔ CLI launch: omitting --task records an empty string, not null (28.510595ms) ✔ CLI launch: omitting --task records an empty string, not null (31.44052ms)
✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (82.272699ms) ✔ CLI seat task: updates only the task after a launch, and refuses on an unlaunched seat (84.00092ms)
✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (136.848395ms) ✔ CLI refusals: no args, unknown flag, missing config, already-registered env, and exec failure (136.324119ms)
✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.370135ms) ✔ samePath: equal paths, symlinked dirs, distinct dirs, and non-strings (0.391021ms)
✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.574248ms) ✔ resolveSetBy: explicit --by wins over the environment; absent or empty environment gives unknown; invalid explicit or environment values refuse with exit 4 (0.580046ms)
✔ 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.756508ms) ✔ 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.759877ms)
✔ 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.859621ms) ✔ 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.888353ms)
✔ 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 (265.483659ms) ✔ 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.866081ms)
✔ family: exactly one launch.max key in the model name, else null (0.688496ms) ✔ family: exactly one launch.max key in the model name, else null (0.638066ms)
✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.645819ms) ✔ sessionEnv passes only the allowlist, the repo's bin on PATH, and the run id (0.662147ms)
✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.455045ms) ✔ newRun: short ids, 0700 directories, and a refusal when the socket path won't fit (1.475413ms)
✔ session file and launch log: 0600, the session file written once (0.94776ms) ✔ session file and launch log: 0600, the session file written once (0.983961ms)
✔ endReason maps the runner's exit codes; a signal is killed (0.112724ms) ✔ endReason maps the runner's exit codes; a signal is killed (0.119377ms)
✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.128494ms) ✔ Registry mirrors to sessions.json; readSessions marks live entries; bad files refuse (1.159632ms)
✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (0.992954ms) ✔ stopSession refuses an unknown run, reports a stale one, and won't signal a pid that isn't a runner (1.02137ms)
✔ a session runs under unshare as pid 1 of its namespace, claims, answers, and stops on mosaic stop (266.652798ms) ✔ a session runs under unshare as pid 1 of its namespace, claims, answers, and stops on mosaic stop (246.698392ms)
ℹ tests 27 ℹ tests 27
ℹ suites 0 ℹ suites 0
ℹ pass 27 ℹ pass 27
@@ -32,4 +32,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 715.303268 ℹ duration_ms 720.831355
+52 -52
View File
@@ -1,54 +1,54 @@
✔ the boot config is checked before anything starts (197.479775ms) ✔ the boot config is checked before anything starts (92.946154ms)
✔ a business with no tracker entry refuses task verbs (139.742344ms) ✔ a business with no tracker entry refuses task verbs (129.120807ms)
✔ credential.expiring and .expired are recorded once per instance (219.539472ms) ✔ credential.expiring and .expired are recorded once per instance (246.504076ms)
✔ a token file that changes on disk records credential.changed (110.804248ms) ✔ a token file that changes on disk records credential.changed (108.226458ms)
✔ autostart polls, reconciles and retries a startup the tracker was down for (137.469261ms) ✔ autostart polls, reconciles and retries a startup the tracker was down for (158.650123ms)
✔ a refusal a restart must clear is not retried by the poll (106.393381ms) ✔ a refusal a restart must clear is not retried by the poll (95.659816ms)
✔ a poll that fires while two are queued is dropped (102.177258ms) ✔ a poll that fires while two are queued is dropped (81.991724ms)
✔ close waits for a running verb and refuses one that has not started (228.556608ms) ✔ close waits for a running verb and refuses one that has not started (219.401264ms)
✔ the bundled Vikunja is the pinned upstream image the runbook names (0.795107ms) ✔ the bundled Vikunja is the pinned upstream image the runbook names (0.718691ms)
✔ every published port is on 127.0.0.1, and no secret is in the file (0.32377ms) ✔ every published port is on 127.0.0.1, and no secret is in the file (0.269078ms)
✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (494.146398ms) ✔ the fake answers each route with the statuses and shapes Vikunja v2.7.0 sent (410.185085ms)
✔ the recorded task bodies pass the checks S3 applies to every read (0.538607ms) ✔ the recorded task bodies pass the checks S3 applies to every read (0.494185ms)
✔ the client works against the fake over real HTTP with the platform fetch (248.265171ms) ✔ the client works against the fake over real HTTP with the platform fetch (249.40339ms)
✔ a correct install starts, and the first reconcile records tasks that already exist (214.536073ms) ✔ a correct install starts, and the first reconcile records tasks that already exist (114.021264ms)
✔ verbs refuse while a business is starting and after startup refused it (148.912633ms) ✔ verbs refuse while a business is starting and after startup refused it (167.462238ms)
✔ startup refuses a token that can do more than its role needs (410.384994ms) ✔ startup refuses a token that can do more than its role needs (408.839768ms)
✔ startup refuses an unsupported version and flags an untested one (320.020087ms) ✔ startup refuses an unsupported version and flags an untested one (304.62854ms)
✔ startup refuses a board that the runbook did not install (381.770113ms) ✔ startup refuses a board that the runbook did not install (329.127035ms)
✔ startup refuses a project the sync bot cannot read (97.801388ms) ✔ startup refuses a project the sync bot cannot read (78.086158ms)
✔ startup refuses a configured label the pm bot cannot see (81.711336ms) ✔ startup refuses a configured label the pm bot cannot see (87.537102ms)
✔ startup refuses an expired credential and a missing sync credential (221.987872ms) ✔ startup refuses an expired credential and a missing sync credential (182.870691ms)
✔ an unreachable tracker refuses with tracker-unavailable (89.937615ms) ✔ an unreachable tracker refuses with tracker-unavailable (101.008667ms)
✔ an edit in the UI is recorded once, with the fields that changed (330.854999ms) ✔ an edit in the UI is recorded once, with the fields that changed (196.851407ms)
✔ a move between open buckets is seen on the board, though updated does not change (190.310234ms) ✔ a move between open buckets is seen on the board, though updated does not change (217.61436ms)
✔ a person's comment is counted and a bot's is not (288.545396ms) ✔ a person's comment is counted and a bot's is not (270.709187ms)
✔ the hourly reconcile catches a comment through comment_count (228.898375ms) ✔ the hourly reconcile catches a comment through comment_count (231.498755ms)
✔ a task closed in the UI leaves the open view with its done bucket (429.734775ms) ✔ a task closed in the UI leaves the open view with its done bucket (400.619607ms)
✔ a task that leaves the board is recorded as deleted, moved or out of reach (287.164684ms) ✔ a task that leaves the board is recorded as deleted, moved or out of reach (278.223735ms)
✔ a poll that read before a verb wrote does not overwrite the verb (176.826276ms) ✔ a poll that read before a verb wrote does not overwrite the verb (189.312329ms)
✔ a tracker fault during a tick is reported and the next tick catches up (155.192537ms) ✔ a tracker fault during a tick is reported and the next tick catches up (184.458181ms)
✔ a malformed answer refuses the tick with tracker-shape (125.233379ms) ✔ a malformed answer refuses the tick with tracker-shape (132.48211ms)
✔ no token value reaches the database, the log or a refusal (283.776287ms) ✔ no token value reaches the database, the log or a refusal (318.139297ms)
✔ the first look at a task counts only comments inside the window (146.045513ms) ✔ the first look at a task counts only comments inside the window (152.842231ms)
✔ task.create needs a recorded human request and a requirement id (319.485088ms) ✔ task.create needs a recorded human request and a requirement id (203.480924ms)
✔ only labels named in the business file can be written (228.589076ms) ✔ only labels named in the business file can be written (263.346377ms)
✔ task.schedule sets and clears a due date and relations (308.501904ms) ✔ task.schedule sets and clears a due date and relations (322.323284ms)
✔ assign and reassign move the role bots and record task.assigned (329.197503ms) ✔ assign and reassign move the role bots and record task.assigned (315.730512ms)
✔ task.update.assigned is for the assignee and records task.state (317.345199ms) ✔ task.update.assigned is for the assignee and records task.state (310.311839ms)
✔ a wrong expected digest records task.conflict and writes nothing (235.892284ms) ✔ a wrong expected digest records task.conflict and writes nothing (197.657449ms)
✔ a cross-role verb needs a resolved decision, used once (253.179147ms) ✔ a cross-role verb needs a resolved decision, used once (292.277958ms)
✔ task.close needs a verdict; after it every verb refuses with task-done (234.270236ms) ✔ task.close needs a verdict; after it every verb refuses with task-done (251.206961ms)
✔ a lost answer is settled by a re-read and never retried (232.558998ms) ✔ a lost answer is settled by a re-read and never retried (288.544452ms)
✔ a create whose answer is lost is reported uncertain, and the poll finds the task (194.4704ms) ✔ a create whose answer is lost is reported uncertain, and the poll finds the task (166.984314ms)
✔ a task the sync bot cannot read refuses and records nothing (132.208983ms) ✔ a task the sync bot cannot read refuses and records nothing (95.348804ms)
✔ verbs and polls for one business run one at a time (185.004946ms) ✔ verbs and polls for one business run one at a time (163.878304ms)
✔ a due date with milliseconds is written to the second (247.995644ms) ✔ a due date with milliseconds is written to the second (173.537098ms)
✔ every write landed and the final read failed: the verb succeeds and records what it wrote (146.862751ms) ✔ every write landed and the final read failed: the verb succeeds and records what it wrote (113.49429ms)
✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (110.616531ms) ✔ some writes landed and the final read failed: write-uncertain, and nothing is recorded (93.119112ms)
✔ a create whose final read fails succeeds and records task.created (161.566382ms) ✔ a create whose final read fails succeeds and records task.created (93.729323ms)
✔ an edit between the last write and the final read shows as external on the next poll (155.139858ms) ✔ an edit between the last write and the final read shows as external on the next poll (139.563322ms)
✔ task.created is recorded when a later label write fails (124.486898ms) ✔ task.created is recorded when a later label write fails (112.601508ms)
ℹ tests 51 ℹ tests 51
ℹ suites 0 ℹ suites 0
ℹ pass 51 ℹ pass 51
@@ -56,4 +56,4 @@
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 4013.267787 ℹ duration_ms 3678.817998
+25 -17
View File
@@ -1,23 +1,31 @@
✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3622.121735ms) ✔ browser edge states: loading, empty, malformed, stale, hostile/long values, in-flight reply and appearance fallback (3866.262552ms)
Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286} Rendered contrast: {"failures":[],"count":330,"lowest":4.504658476260286}
✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (3565.038836ms) ✔ served Console browser: real board fixtures, keyboard, drafts, receipts, themes, 320px and failures (4896.033795ms)
✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3611.776637ms) ✔ S5 pages in a browser: real broker rows, inert text, Copy, routes, failures, layout and no writes (5937.94953ms)
✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (1686.895048ms) ✔ bus routes serve the four reads from the reader view, unescaped JSON for the page to escape (260.160625ms)
✔ conversation view: a newer session with no readable history keeps the marker (1116.328238ms) ✔ bus routes are GET-only, have no write verb, and keep the same-origin checks (110.02109ms)
✔ conversation view: seats without history say so and offer no reply (851.317089ms) ✔ bus refusals map to statuses and never carry a path or a stack (112.994569ms)
✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3608.726306ms) ✔ humanCall speaks the human transport protocol without blocking the server (2037.405502ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54301.56092ms) ✔ busReader takes --business, else the live bus host, else refuses (160.353205ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3605.029743ms) ✔ humanCall kills a transport that runs past its timeout (528.132694ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (21936.08607ms) ✔ serve refuses a --business value that is not a business id (154.831599ms)
✔ loopback host and board origin fail closed (6.73058ms) ✔ conversation view: full history, collapsed tools, hidden thinking, inert hostile content, malformed and reconcile markers (3833.080507ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (84.22649ms) ✔ conversation view: a fork keeps the open branch, says so, and opens the new one on request (2510.048378ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (95.864801ms) ✔ conversation view: a newer session with no readable history keeps the marker (1044.061451ms)
✔ unreachable board reports URL; CLI rejects unsupported options (1030.704712ms) ✔ conversation view: seats without history say so and offer no reply (879.903005ms)
ℹ tests 14 ✔ Discord row through real board/WebUI: independent brake/liveness, no Reply, literal content (3870.094794ms)
✔ return flow through the conversation view: send, tool call, delayed result, peer message, exact long answers, relaunch (54107.983822ms)
✔ both presentations replace old activity with relaunch notice, label retained history, then resume after new activity (3838.04821ms)
✔ reported return flow and relative Age: reply sent from the inspector, then the new answer appears there without manual refresh (22664.61891ms)
✔ loopback host and board origin fail closed (7.053813ms)
✔ real board fixture passes through WebUI; assets and isolated seen/reply work (100.777387ms)
✔ proxy preserves exact request bytes, status and receipt, rejects forms and malformed JSON, never follows redirect (92.335193ms)
✔ unreachable board reports URL; CLI rejects unsupported options (817.696846ms)
ℹ tests 22
ℹ suites 0 ℹ suites 0
ℹ pass 14 ℹ pass 22
ℹ fail 0 ℹ fail 0
ℹ cancelled 0 ℹ cancelled 0
ℹ skipped 0 ℹ skipped 0
ℹ todo 0 ℹ todo 0
ℹ duration_ms 54764.629902 ℹ duration_ms 54480.956526
@@ -0,0 +1,12 @@
== MODE=read given workspace <R>/data/ws, real <R>/deep/store/ws
exit 0; stdout: mock answer; stderr:
POST /v1/messages: 2
tool_result sent back: [{"is_error":true,"content":"PreToolUse:Read hook error: [timeout -k 2 10 /usr/bin/node /home/jwoltje/filbert-scratch/s6-build/packages/harness/src/claude-gate.mjs <R>/bundle/policy.json || exit 2]: mosaic gate: Read path is outside the workspace: <R>/deep/data/ws/secret.txt\n"}]
outside after the run: secret.txt
workspace after the run:
== MODE=write given workspace <R>/data/ws, real <R>/deep/store/ws
exit 0; stdout: mock answer; stderr:
POST /v1/messages: 2
tool_result sent back: [{"is_error":true,"content":"PreToolUse:Write hook error: [timeout -k 2 10 /usr/bin/node /home/jwoltje/filbert-scratch/s6-build/packages/harness/src/claude-gate.mjs <R>/bundle/policy.json || exit 2]: mosaic gate: Write path is outside the workspace: <R>/deep/data/ws/planted.txt\n"}]
outside after the run: secret.txt
workspace after the run:
@@ -0,0 +1,12 @@
== MODE=read given workspace <R>/data/ws, real <R>/deep/store/ws
gate decide: {"allow":false,"reason":"mosaic gate: read path is outside the workspace: ../../data/ws/secret.txt"}
exit 0; stderr:
tool_result sent back: [{"is_error":true,"content":"mosaic gate: read path is outside the workspace: ../../data/ws/secret.txt"}]
outside after the run: secret.txt
workspace after the run:
== MODE=write given workspace <R>/data/ws, real <R>/deep/store/ws
gate decide: {"allow":false,"reason":"mosaic gate: write path is outside the workspace: ../../data/ws/planted.txt"}
exit 0; stderr:
tool_result sent back: [{"is_error":true,"content":"mosaic gate: write path is outside the workspace: ../../data/ws/planted.txt"}]
outside after the run: secret.txt
workspace after the run:
+3 -3
View File
@@ -2,15 +2,15 @@ node-business exit=0 ℹ pass 60 ℹ fail 0
node-bus exit=0 ℹ pass 74 ℹ fail 0 node-bus exit=0 ℹ pass 74 ℹ fail 0
node-cli exit=0 ℹ pass 83 ℹ fail 0 node-cli exit=0 ℹ pass 83 ℹ fail 0
node-control-board exit=0 ℹ pass 124 ℹ fail 0 node-control-board exit=0 ℹ pass 124 ℹ fail 0
node-conversation exit=0 ℹ pass 152 ℹ fail 0 node-conversation exit=0 ℹ pass 161 ℹ fail 0
node-discord exit=0 ℹ pass 178 ℹ fail 0 node-discord exit=0 ℹ pass 178 ℹ fail 0
node-harness exit=0 ℹ pass 53 ℹ fail 0 node-harness exit=0 ℹ pass 56 ℹ fail 0
node-ledger exit=0 ℹ pass 78 ℹ fail 0 node-ledger exit=0 ℹ pass 78 ℹ fail 0
node-mosaic exit=0 ℹ pass 69 ℹ fail 0 node-mosaic exit=0 ℹ pass 69 ℹ fail 0
node-queue exit=0 ℹ pass 148 ℹ fail 0 node-queue exit=0 ℹ pass 148 ℹ fail 0
node-seat exit=0 ℹ pass 27 ℹ fail 0 node-seat exit=0 ℹ pass 27 ℹ fail 0
node-tasks exit=0 ℹ pass 51 ℹ fail 0 node-tasks exit=0 ℹ pass 51 ℹ fail 0
node-webui exit=0 ℹ pass 14 ℹ fail 0 node-webui exit=0 ℹ pass 22 ℹ fail 0
test-auth exit=0 selftest: 15 passed, 0 failed test-auth exit=0 selftest: 15 passed, 0 failed
test-conductor exit=0 selftest: 17 passed, 0 failed test-conductor exit=0 selftest: 17 passed, 0 failed
test-config exit=0 selftest: 24 passed, 0 failed test-config exit=0 selftest: 24 passed, 0 failed
+34 -2
View File
@@ -1,7 +1,39 @@
On branch refactor Note: switching to '0a4c8f13b09b8882ea55f6061d26949330385ee8'.
Your branch is up to date with 'origin/refactor'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
Not currently on any branch.
nothing to commit, working tree clean nothing to commit, working tree clean
Note: switching to '0a4c8f13b09b8882ea55f6061d26949330385ee8'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
OK dry-run: allowed change, exit 0, nothing committed (exit 0) OK dry-run: allowed change, exit 0, nothing committed (exit 0)
OK dry-run committed nothing OK dry-run committed nothing
OK apply: allowed change exits 0 (exit 0) OK apply: allowed change exits 0 (exit 0)
@@ -8,7 +8,7 @@ OK check detects an extra destination directory
OK check rejects a destination symlink OK check rejects a destination symlink
OK sync accepts a canonical source update OK sync accepts a canonical source update
OK updated installation matches canonical source OK updated installation matches canonical source
scripts/test-extension-package.sh: line 14: 2015159 Killed "$@" > /dev/null 2>&1 scripts/test-extension-package.sh: line 14: 2783136 Killed "$@" > /dev/null 2>&1
OK forced interruption kills the replacing process OK forced interruption kills the replacing process
OK next invocation recovers old consistent installation OK next invocation recovers old consistent installation
OK interrupted replacement rolled back OK interrupted replacement rolled back
+1 -1
View File
@@ -30,6 +30,6 @@ OK packages/queue declares no dependencies
ℹ fail 0 ℹ fail 0
OK node --test packages/queue/tests/ OK node --test packages/queue/tests/
OK scripts/mosaic queue help OK scripts/mosaic queue help
skip queue verify and render --check: this checkout (/home/jwoltje/filbert-scratch/s6-gate-r3) is not the queue's canonical root (/mnt/storage/src/mosaic-stack) skip queue verify and render --check: this checkout (/home/jwoltje/filbert-scratch/s6-gate-r4) is not the queue's canonical root (/mnt/storage/src/mosaic-stack)
queue suite: 27 passed, 0 failed queue suite: 27 passed, 0 failed
+22 -20
View File
@@ -1,29 +1,31 @@
5dc9e67c9ce42b86c40d83c8b1aa8d5ba93d6e8b51bb1f4d2964f4b270ee52aa base.txt 5dc9e67c9ce42b86c40d83c8b1aa8d5ba93d6e8b51bb1f4d2964f4b270ee52aa base.txt
d9ae770c156020b3b536f06f0ecfa6e550423343f85c8b4566fa90fd9a650319 BUILD.md 45a1a67b9c0c20fa911491345a94c28296b614c5ff9526d7e5442e8e787e2c6f BUILD.md
7b63583de7a701cfb835ffabe4118b2624df49cfcaedf7c7691b097695b8907e build.patch f7e83e5171dd2de01fb55624fdbbb3f0d29c6c8da465298151b89dc6aeee0422 build.patch
9f0593af17902e6ec084987175a1e48affd9c71568cb2eeacc7b4cd1ef165cbe candidate-manifest.sha256 08a78972e316cb3b9cba2050489bd65b2c0b1ec95eb7de9b91c266e89a0d0d66 candidate-manifest.sha256
c6309079b471a4b304ba55679bfa2ce7f1e145137ec784f2e35ee7330df844ea files.txt c6309079b471a4b304ba55679bfa2ce7f1e145137ec784f2e35ee7330df844ea files.txt
8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/base-test-task.txt 8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/base-test-task.txt
08ff9447c1cdff32245cb957876aff62406b5d0343bd50bf0924666523a92de0 out/node-business.txt 5823cfb20738fa2cf2a148d6920ac4180e0c195a31d835f6d8c2015bc786d37b out/node-business.txt
454d05e1e09f87988d90d302deb0a38790c195973009e14073149e0df3ca26ee out/node-bus.txt b113a27fc69eb6c5e33b67f6aad4704ae9828ead116366cd1067c77f4b8adf10 out/node-bus.txt
155a45072ac3e2068891db10ca9dc7de4da0d58a4e05945f89e61e354444069a out/node-cli.txt af74a06ba2122ede59f0adf1d37a1b4c108569fd2fbbe00533250549e39640d6 out/node-cli.txt
1da902b53efce461be93f6841ea257ec08ce345150e3d9a1d76e98f1c65a3ff4 out/node-control-board.txt a00045831c7d8c0a1f75aff7997f41b7a579863bbd66130d7d761a721340428d out/node-control-board.txt
60ad9ef705fe72365e7bb87470045a10ffb604322dcf7f376b6b11946f453657 out/node-conversation.txt 67ade5f3961b254f2da6392a0bfdb479fb011299ffc03a0f986743b5bd2f5f03 out/node-conversation.txt
54bc9eda871683552daffc452d6255789210261dee93dd7250da0d10ce8408c5 out/node-discord.txt 090c2e9e183f51e01cb45b4bc2e8f95e715dd8bf0a976f0a669d18f79849b463 out/node-discord.txt
0518f1253d7ae01547e580a7072d67f93846f438840582e577cd65b746d5c59f out/node-harness.txt e8e4ffa11d1823e99752e2614f29ed06d344ec2a98bdb2769ba601053bf7cbd7 out/node-harness.txt
5d7953f6c6ec388351712adccb5ce97b74921ec53d0dcb122bc218e3625ca9eb out/node-ledger.txt 14f6d61cf2bddb62b0dfb39cd2e2963aa98c0e42f3a45b27e902e5667f388cb6 out/node-ledger.txt
a39c3ebe4da7a99cf89d71b98cb26f5c9cb90dd7c44df15485debd2b9b79d781 out/node-mosaic.txt ab36b5e758eca554649cd8c775ceef64e09f6a81ec588248771600322a39cae3 out/node-mosaic.txt
cff341d0a8e89e5e316deb91eea7ea13813b85385178fc5e3059c26866494a61 out/node-queue.txt d012f3e7814969e12e0b8dcf85d9a31beee113aa1575727b61d915312421fe3b out/node-queue.txt
c2290fe1e3d10953c590af952dc22591d7402cdabc64f718cec0fb49f95ecb5b out/node-seat.txt 94cbfd0823506974e067248fe0516b55577a5b0f61ff43ffba2f1a8d8cfc38f6 out/node-seat.txt
24ce87d27bd52f5554a795da5dbfe409351ec0e265faa68b455fd7863e6b743c out/node-tasks.txt a502a780bebf4715f4630ca3a6d33666ea77f04ee838bd8de091a7979f31b08c out/node-tasks.txt
fa73b82e2d48e88d23191f8c7d7a7b5aba283e6675b1d0db63425d764fd43227 out/node-webui.txt cfc7ea0834415187e031e1c74a24e40e1295c5e3f54843a6625aa3d8ed8c3c53 out/node-webui.txt
195c9716b01d1b25b2de36c99dd1cf970e579fdf9c108b40fc83d910db121769 out/summary.txt 36f628d1a329efad68bca0c0188217fa3c64d9eccced483e4ae8e29a2d1d2258 out/probe-claude-cwd-dotdot.txt
f6cdb3ed25870314c0208798d6aedd409a8935858457b86f917cbc316e5e8dd0 out/probe-pi-cwd-dotdot.txt
a9488308d3b1297b238f4dd0024b300b3c857934bcbcf1d448cab134b20b7d86 out/summary.txt
f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/test-auth.txt f91fc95a173339137f981e24fbf630acfde957e32c3cd71e28304532083c6a77 out/test-auth.txt
e1206366f07f3d9029530dcdcb55c0282d3f052adc60fc7fd51642d5d4ec3600 out/test-conductor.txt 375f995f256ddde6fa10fd0c53e6bf74329e3984fc417af45102ffe4c6580646 out/test-conductor.txt
52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/test-config.txt 52d211444489ea729dafc3c9745d4474d9dfd50f41055cbad792424197f04073 out/test-config.txt
7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/test-discord.txt 7d5016ded1994048642f8b9ae845e52e8af4a069ba05c056c8f580a4589bacbe out/test-discord.txt
4726725a020747117f43feaffffd04935633fd809e33306b97146e688233ec25 out/test-extension-package.txt 52438293b140f37c304d8ca1be30d9542d2388767d00cf56c8aadb07ef487999 out/test-extension-package.txt
83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/test-foundation.txt 83dfaa21c4e4ad941088efab3f69e87ebd2f6bd3cc72ede938e21dc06da8a34f out/test-foundation.txt
5bdf623f67f231a226bdc707bfaec9796c3c227c9ffedd95953693714e6182dd out/test-queue.txt 440adee8e4bba7105aeaa43aff142e8e0b5842052a537ee4fb516ca854d89a14 out/test-queue.txt
6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/test-release.txt 6183e6b9b05edb497e92ffd09a19bba49c6f656c750ecb2acc283021113d1a98 out/test-release.txt
8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/test-task.txt 8df77c49bf82833efa56d42401a4a06f20931b7a76dd5eed3a73fdec60e92e79 out/test-task.txt